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

 更新时间:2021年12月12日 15:43:01   作者:既然头发留不住  
目前有个需求,需要使用不同的数据源,例如某业务要用A数据源,另一个业务要用B数据源,所以下面这篇文章主要给大家介绍了关于Springboot动态切换数据源的具体实现与原理分析,需要的朋友可以参考下

前言

在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);这个方法指定数据源对应的枚举类即可。

原理分析:

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

这个类里维护了两个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类。

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

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

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

第五个resolvedDataSources也是一个Map对象,这里是存放指定数据源解析后的DataSource对象。

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

总结

相关文章

  • Java语言实现简单FTP软件 FTP连接管理模块实现(8)

    Java语言实现简单FTP软件 FTP连接管理模块实现(8)

    这篇文章主要为大家详细介绍了Java语言实现简单FTP软件,FTP连接管理模块的实现方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • 浅析Java IO相关知识点

    浅析Java IO相关知识点

    本篇文章给大家分享了关于java io的一些相关知识点以及相关内容,对此有需要的朋友可以学习参考下。
    2018-05-05
  • Mybatis实现ResultMap结果集

    Mybatis实现ResultMap结果集

    本文主要介绍了Mybatis实现ResultMap结果集,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • springboot整合mybatis-plus 实现分页查询功能

    springboot整合mybatis-plus 实现分页查询功能

    这篇文章主要介绍了springboot整合mybatis-plus 实现分页查询功能,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • Java19新特性中结构化并发的使用

    Java19新特性中结构化并发的使用

    Java19在并发编程领域引入了一个全新的概念:结构化并发,这一特性旨在简化并发任务的管理,提升多线程程序的可维护性和安全性,使其生命周期和控制流更加有序和明确,感兴趣的可以了解一下
    2024-09-09
  • 详解Spring AOP的原理与实现方式

    详解Spring AOP的原理与实现方式

    Spring框架是一个功能强大且灵活的企业级应用程序开发框架,其中最重要的特性之一就是面向切面编程(AOP),我们今天这篇文章将从源码和案例的角度详细介绍Spring AOP的思想、原理和实现方式
    2023-07-07
  • Java实现解析JSON大文件JsonReader工具详解

    Java实现解析JSON大文件JsonReader工具详解

    这篇文章主要介绍了Java实现解析JSON大文件的工具JsonReader使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-01-01
  • hibernate中HQL如何调用自定义函数

    hibernate中HQL如何调用自定义函数

    这篇文章主要介绍了hibernate中HQL如何调用自定义函数问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • HashMap之keyset()方法底层原理解读

    HashMap之keyset()方法底层原理解读

    这篇文章主要介绍了HashMap之keyset()方法底层原理解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • java中this的n种使用方法

    java中this的n种使用方法

    this可能是几乎所有有一点面向对象思想的语言都会引用到的变量,this有多少种用法。下面小编给大家带来了java中this的n种使用方法,感兴趣的朋友一起看看吧
    2018-08-08

最新评论