欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Springboot动态切换数据源的具体实现与原理分析

程序员文章站 2022-06-24 16:54:50
目录前言具体实现:原理分析:总结前言在springboot项目中只需一句代码即可实现多个数据源之间的切换:// 切换sqlserver数据源:datasourcecontextholder.setda...

前言

在springboot项目中只需一句代码即可实现多个数据源之间的切换:

// 切换sqlserver数据源:
datasourcecontextholder.setdatabasetype(datasourceenum.sqlserver_datasource);
......
// 切换mysql数据源    
datasourcecontextholder.setdatabasetype(datasourceenum.mysql_datasource);

具体实现:

本实例基于springboot2.5+版本实现。

1.配置数据源:

在配置文件中配置多个数据源的连接信息,用不同的前缀作为区别:

# sqlserver数据源1:前缀为:spring.datasource.sqlserver
spring.datasource.sqlserver.driver-class-name=com.microsoft.sqlserver.jdbc.sqlserverdriver
spring.datasource.sqlserver.jdbc-url=jdbc:sqlserver://localhost:1433;databasename=test
spring.datasource.sqlserver.username=sa
spring.datasource.sqlserver.password=sa
# mysql数据源1:前缀为:spring.datasource.mysql
spring.datasource.mysql.driver-class-name=com.mysql.jdbc.driver
spring.datasource.mysql.jdbc-url=jdbc:mysql://localhost:3306/test?useunicode=true&characterencoding=utf-8&usessl=false&allowmultiqueries=true
spring.datasource.mysql.username=root
spring.datasource.mysql.password=root
# sqllite数据源1:前缀为:spring.datasource.sqlite
spring.datasource.sqlite.driver-class-name=org.sqlite.jdbc
spring.datasource.sqlite.jdbc-url=jdbc:sqlite:d://sqllite//test.db
spring.datasource.sqlite.username=
spring.datasource.sqlite.password=
# sqlserver数据源2:前缀为:spring.datasource.sqlserver2
spring.datasource.sqlserver2.driver-class-name=com.microsoft.sqlserver.jdbc.sqlserverdriver
spring.datasource.sqlserver2.jdbc-url=jdbc:sqlserver://localhost;databasename=test1
spring.datasource.sqlserver2.username=sa
spring.datasource.sqlserver2.password=sa
# 配置数据库连接池信息
spring.datasource.hikari.maximum-pool-size=32
spring.datasource.hikari.minimum-idle=16

2.新建枚举类datasourceenum,有几个数据源对应设置几个枚举类。

public enum datasourceenum {
    mysql_datasource,
    sqlserver_datasource,
    sqlserver2_datasource,
    sqllite_datasource
}

3.新建数据库切换工具类datasourcecontextholder,这里通过threadlocal类型的变量来存储当前数据源枚举类,同时能够保证线程安全。

public class datasourcecontextholder {

    /**
     * 通过threadlocal保证线程安全
     */
    private static final threadlocal<datasourceenum> contextholder = new threadlocal<>();

    /**
     * 设置数据源变量
     * @param datasourceenum 数据源变量
     */
    public static void setdatabasetype(datasourceenum datasourceenum) {
        system.out.println("修改数据源为:" + datasourceenum);
        contextholder.set(datasourceenum);
    }

    /**
     * 获取数据源变量
     * @return 数据源变量
     */
    public static datasourceenum getdatabasetype() {
        datasourceenum datasourceenum = contextholder.get() == null ? datasourceenum.mysql_datasource : contextholder.get();
        system.out.println("当前数据源的类型为:" + datasourceenum);
        return datasourceenum;
    }

    /**
     * 清空数据类型
     */
    public static void cleardatabasetype() {
        contextholder.remove();
    }

4.新建dynamicdatasource类继承abstractroutingdatasource类,并实现determinecurrentlookupkey方法,该方法是指定当前默认数据源的方法。

public class dynamicdatasource extends abstractroutingdatasource {
    @override
    protected object determinecurrentlookupkey() {
        return datasourcecontextholder.getdatabasetype();
    }
}

这个类看似内容不多,但其实继承了abstractroutingdatasource类是实现动态切换数据源的关键。

5.新建datasourceconfig类用来创建bean的实例,其中包括各数据源的datasource实例,dynamicdatasource实例以及跟mybatis相关的sqlsessionfactory或spring的jdbctemplate实例。

@configuration
public class datasourceconfig {
    @bean(name = "sqlserverdatasource")
    @configurationproperties(prefix = "spring.datasource.sqlserver")
    public datasource getdatesource1() {
        return datasourcebuilder.create().build();
    }

    @bean(name = "sqlserver2datasource")
    @configurationproperties(prefix = "spring.datasource.sqlserver2")
    public datasource getdatesource11() {
        return datasourcebuilder.create().build();
    }

    @bean(name = "mysqldatasource")
    @configurationproperties(prefix = "spring.datasource.mysql")
    public datasource getdatesource2() {
        return datasourcebuilder.create().build();
    }


    @bean(name = "sqllitedatasource")
    @configurationproperties(prefix = "spring.datasource.sqlite")
    public datasource getdatesource3() {
        return datasourcebuilder.create().build();
    }

    @bean(name = "dynamicdatasource")
    public dynamicdatasource datasource(@qualifier("sqlserverdatasource") datasource sqlserverdatasource,
                                        @qualifier("sqlserver2datasource") datasource sqlserver2datasource,
                                        @qualifier("mysqldatasource") datasource mysqldatasource,
                                        @qualifier("sqllitedatasource") datasource sqllitedatasource) {
        //配置多数据源
        map<object, object> targetdatasource = new hashmap<>();
        targetdatasource.put(datasourceenum.sqlserver_datasource, sqlserverdatasource);
        targetdatasource.put(datasourceenum.mysql_datasource, mysqldatasource);
        targetdatasource.put(datasourceenum.sqllite_datasource, sqllitedatasource);
        targetdatasource.put(datasourceenum.sqlserver2_datasource, sqlserver2datasource);
        dynamicdatasource datasource = new dynamicdatasource();
        //多数据源
        datasource.settargetdatasources(targetdatasource);
        //默认数据源
        datasource.setdefaulttargetdatasource(sqlserverdatasource);
        return datasource;
    }
    @bean(name = "sqlsessionfactory")
    public sqlsessionfactory test1sqlsessionfactory(@qualifier("dynamicdatasource") datasource dynamicdatasource)
            throws exception {
        sqlsessionfactorybean bean = new sqlsessionfactorybean();
        bean.setdatasource(dynamicdatasource);
        return bean.getobject();
    }

    @bean(name = "jdbctemplate")
    public jdbctemplate test1jdbctemplate(@qualifier("dynamicdatasource") datasource dynamicdatasource) {
        return new jdbctemplate(dynamicdatasource);
    }
}

这样就把我们切换数据库锁需要的bean全部交给spring容器中了,使用时直接通过datasourcecontextholder.setdatabasetype(datasourceenum datasourceenum);这个方法指定数据源对应的枚举类即可。

原理分析:

Springboot动态切换数据源的具体实现与原理分析

其实我们新建数据库连接的时候也是通过datasource来获取连接的,这里的abstractroutingdatasource也是通过了datasource中的getconnection方法来获取连接的。

Springboot动态切换数据源的具体实现与原理分析

这个类里维护了两个map来存储数据库连接信息:

@nullable
private map<object, object> targetdatasources; 

@nullable
private object defaulttargetdatasource;

private boolean lenientfallback = true;

private datasourcelookup datasourcelookup = new jndidatasourcelookup();

@nullable
private map<object, datasource> resolveddatasources;

@nullable
private datasource resolveddefaultdatasource;

下面对上面的几个属性进行说明:

其中第一个targetdatasources是一个map对象,在我们上面第五步创建dynamicdatasource实例的时候将多个数据源的datasource类,放入到这个map中去,这里的key是枚举类,values就是datasource类。

Springboot动态切换数据源的具体实现与原理分析

第二个defaulttargetdatasource是默认的数据源,就是dynamicdatasource中唯一重写的方法来给这个对象赋值的。

Springboot动态切换数据源的具体实现与原理分析

第三个lenientfallback是一个标识,是当指定数据源不存在的时候是否采用默认数据源,默认是true,设置为false之后如果找不到指定数据源将会返回null.

Springboot动态切换数据源的具体实现与原理分析

第四个datasourcelookup是用来解析指定的数据源对象为datasource实例的。默认是jndidatasourcelookup实例,继承自datasourcelookup接口。

第五个resolveddatasources也是一个map对象,这里是存放指定数据源解析后的datasource对象。

Springboot动态切换数据源的具体实现与原理分析

第六个resolveddefaultdatasource是默认的解析后的datasource数据源对象上面的getconnection方法就是从这个变量中拿到datasource实例并获取连接的。

Springboot动态切换数据源的具体实现与原理分析

总结