当前位置: 代码网 > it编程>编程语言>Java > SpringBoot配置动态数据源的实战详解

SpringBoot配置动态数据源的实战详解

2024年08月25日 Java 我要评论
数据源切换方法spring对数据源的管理类似于策略模式,不懂策略模式也没关系,其实就是有一个全局的键值对,类型是map<string, datasource>。当jdbc操作数据库之时,会

数据源切换方法

spring对数据源的管理类似于策略模式,不懂策略模式也没关系,其实就是有一个全局的键值对,类型是map<string, datasource>。当jdbc操作数据库之时,会根据不同的key值选择不同的数据源。而这个key值可以放到方法的注解里。

所以切换数据源的思路就是让jdbc在获取数据源时根据key获取到要切换的数据源。

jdbc提供了abstractroutingdatasource抽象类,类名意思是数据源路由,该类提供了一个抽象方法determinecurrentlookupkey(),切换数据源时jdbc会调用这个方法获取数据源的key,所以只需要实现该方法,改变该方法中返回的key值即可。

源码解读

1.从类关系图中可以看出abstractroutingdatasource类实现了datasource接口,后者有两个getconnection()方法,即获取db连接的作用。

在这里插入图片描述

2.abstractroutingdatasource实现了这两个方法

在这里插入图片描述

其中determinetargetdatasource()方法的作用就是获取实际的数据源,其内部调用了determinecurrentlookupkey()方法,取到当前设定的key,通过key在上下文this.resolveddatasources属性中尝试获取datasource对象,这个对象即当前连接的数据源

在这里插入图片描述

3.那么this.resolveddatasources在哪里维护呢? 继续在abstractroutingdatasource类里找,可以找到afterpropertiesset()方法,这个方法是initializingbean接口的,作用是在bean的所有属性设置完成后便会调用此方法。可以看到this.resolveddatasources是从this.targetdatasources取的信息。

在这里插入图片描述

所以只需要改变this.targetdatasources,即可改变this.resolveddatasources;后续改变determinecurrentlookupkey()的返回值(key),在调用getconnection()时即可获取到指定的数据源

实现方式:注解+切面

别看步骤挺多,但其实很容易理解和使用

1.配置文件示例:

spring:
  datasource: 
    master: # 数据源master
      jdbc-url: jdbc:mysql://localhost:3306/master?characterencoding=utf8&useunicode=true&usessl=false&servertimezone=gmt%2b8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.driver
    db1: # 数据源1
      jdbc-url: jdbc:mysql://localhost:3306/db2?characterencoding=utf8&useunicode=true&usessl=false&servertimezone=gmt%2b8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.driver

2.创建数据源配置类

创建数据源配置类(我这里为了方便区分就为每一个数据源创建了一个配置类,当然也可以把所有的数据源配置在一个类里)

@configuration
@enableconfigurationproperties({masterdatasourceproperties.class})
public class masterdatasourceconfig {
    /**
    * 这个masterdatasourceproperties是读取配置文件的类,我这里为了省篇幅就不展示了
    **/
    @autowired
    private masterdatasourceproperties masterdatasourceproperties;

    @bean
    @primary
    public datasource masterdatasource() {
        druiddatasource datasource = new druiddatasource();
        datasource.seturl(masterdatasourceproperties.geturl());
        datasource.setusername(masterdatasourceproperties.getusername());
        datasource.setpassword(aesutil.aesdecode(masterdatasourceproperties.getpassword()));
        datasource.setdriverclassname(masterdatasourceproperties.getdriverclassname());
        ......

        return datasource;
    }
}
@configuration
@enableconfigurationproperties({odsdatasourceproperties.class})
public class db1datasourceconfig {
    /**
    * 这个db1datasourceproperties是读取配置文件的类,我这里为了省篇幅就不展示了
    **/
    @autowired
    private db1datasourceproperties db1datasourceproperties;

    @bean
    public datasource db1datasource() {
        druiddatasource datasource = new druiddatasource();
        datasource.seturl(db1datasourceproperties.geturl());
        datasource.setusername(db1datasourceproperties.getusername());
        datasource.setpassword(aesutil.aesdecode(db1datasourceproperties.getpassword()));
        datasource.setdriverclassname(db1datasourceproperties.getdriverclassname());
        ......

        return datasource;
    }
}

3.创建dynamicdatasource

创建自己的一个dynamicdatasource类(名字任意)继承abstractroutingdatasource,维护数据源,提供切换方法。

public class dynamicdatasource extends abstractroutingdatasource {

    /**
    * 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
    * 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个datasource实现类对象即可
    */
    @override
    protected datasource determinetargetdatasource() {
        return super.determinetargetdatasource();
    }

    /**
    * 如果希望所有数据源在启动配置时就加载好,然后通过设置数据源key值来切换数据,定制这个方法
    */
    @override
    protected object determinecurrentlookupkey() {
        return dynamicdatasourcecontextholder.getdatasourcekey();
    }

    /**
    * 设置默认数据源
    *
    * @param defaultdatasource
    */
    public void setdefaultdatasource(object defaultdatasource) {
        super.setdefaulttargetdatasource(defaultdatasource);
    }

    /**
    * 设置数据源
    *
    * @param datasources
    */
    public void setdatasources(map<object, object> datasources) {
        super.settargetdatasources(datasources);
        // 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
        dynamicdatasourcecontextholder.adddatasourcekeys(datasources.keyset());
    }
}

4.创建数据源上下文处理器dynamicdatasourcecontextholder

创建数据源上下文处理器dynamicdatasourcecontextholder用以存储当前线程需要使用的数据源名称。

public class dynamicdatasourcecontextholder {

    private static final threadlocal<string> contextholder = new threadlocal<string>() {
        /**
         * 将 master 数据源的 key作为默认数据源的 key
         */
        @override
        protected string initialvalue() {
            return "master";
        }
    };


    /**
     * 数据源的 key集合,用于切换时判断数据源是否存在
     */
    public static list<object> datasourcekeys = new arraylist<>();

    /**
     * 切换数据源
     *
     * @param key
     */
    public static void setdatasourcekey(string key) {
        contextholder.set(key);
    }

    /**
     * 获取数据源
     *
     * @return
     */
    public static string getdatasourcekey() {
        return contextholder.get();
    }

    /**
     * 重置数据源
     */
    public static void cleardatasourcekey() {
        contextholder.remove();
    }

    /**
     * 判断是否包含数据源
     *
     * @param key 数据源key
     * @return
     */
    public static boolean containdatasourcekey(string key) {
        return datasourcekeys.contains(key);
    }

    /**
     * 添加数据源keys
     *
     * @param keys
     * @return
     */
    public static boolean adddatasourcekeys(collection<? extends object> keys) {
        return datasourcekeys.addall(keys);
    }
}

5.创建数据源配置类datasourceconfig

创建数据源配置类datasourceconfig,将所有数据源注入到spring容器

@configuration
@enableautoconfiguration(exclude = { datasourceautoconfiguration.class }) // 排除 datasourceautoconfiguration 的自动配置,避免环形调用
public class datasourceconfig {

    @autowired
    private masterdatasourceconfig masterdatasourceconfig;

    @autowired
    private db1datasourceconfig db1datasourceconfig;
    /**
     * 设置动态数据源为主数据源
     *
     * @return
     */
    @bean
    @primary
    public dynamicdatasource datasource() {
        dynamicdatasource dynamicdatasource = new dynamicdatasource();
        // 默认指定的数据源
        dynamicdatasource.setdefaultdatasource(masterdatasourceconfig.masterdatasource());
        // 将数据源设置进map
        map<object, object> datasourcemap = new hashmap<>(8);
        datasourcemap.put(datasourceenum.master.tostring(), masterdatasourceconfig.masterdatasource());
        datasourcemap.put(datasourceenum.db1.tostring(), db1datasourceconfig.db1datasource());
        // 使用 map 保存多个数据源,并设置到动态数据源对象中,这个值最终会在afterpropertiesset中被设置到resolveddatasources上
        dynamicdatasource.setdatasources(datasourcemap);
        return dynamicdatasource;
    }
}

6.创建数据源类型枚举datasourceenum

public enum datasourceenum {

    /**默认类型*/
    master,
    /**db1类型*/
    db1,
    ;
}

7.创建自定义注解@datasource

@target({elementtype.method, elementtype.type})
@retention(retentionpolicy.runtime)
@documented
public @interface datasource {

    /**
     * 数据源key值
     * @return
     */
    datasourceenum value();
}

8.创建切面dynamicdatasourceaspect

@slf4j
@aspect
@order(-1)
@component
public class dynamicdatasourceaspect {

    /**
     * 切换数据源
     *
     * @param point
     * @param datasource
     */
    @before("@annotation(datasource))")
    public void switchdatasource(joinpoint point, datasource datasource) {
        if (!dynamicdatasourcecontextholder.containdatasourcekey(datasource.value().tostring())) {
            log.info("datasource [{}] doesn't exist, use default datasource", datasource.value());
        } else {
            // 切换数据源
            dynamicdatasourcecontextholder.setdatasourcekey(datasource.value().tostring());
            log.info("switch datasource to [{}] in method [{}]", dynamicdatasourcecontextholder.getdatasourcekey(),
                    point.getsignature());
        }
    }

    /**
     * 重置数据源
     *
     * @param point
     * @param datasource
     */
    @after("@annotation(datasource))")
    public void restoredatasource(joinpoint point, datasource datasource) {
        // 将数据源置为默认数据源
        dynamicdatasourcecontextholder.cleardatasourcekey();
        log.info("restore datasource to [{}] in method [{}]", dynamicdatasourcecontextholder.getdatasourcekey(), point.getsignature());
    }
}

如何使用

@override
@datasource(datasourceenum.db1)
public page<audittaskdto> queryaudittask(audittaskquery query) {
    page<audittaskdto> page = basemapper.queryaudittask(query);
    return page;
}

如此就可以直接使用自定义的@datasource注解来切换数据源啦~~~

以上就是springboot配置动态数据源的实战详解的详细内容,更多关于springboot动态数据源的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com