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

作者:既然头发留不住 时间:2022-11-24 13:45:26 

前言

在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动态切换数据源的具体实现与原理分析

来源:https://blog.csdn.net/zhang_java_11/article/details/121626842

标签:springboot,动态,数据源
0
投稿

猜你喜欢

  • 解析Java中如何获取Spring中配置的bean

    2023-07-20 13:35:26
  • asp.net之生成验证码的方法集锦(一)

    2022-09-07 22:37:13
  • 浅谈Java对象禁止使用基本类型

    2022-11-07 19:59:04
  • @RequestBody的使用案例代码

    2021-07-11 16:46:50
  • 完美解决Spring Boot前端的Access-Control-Allow-Origin跨域问题

    2023-09-19 10:16:28
  • Java 注解学习笔记

    2022-12-25 02:40:54
  • SpringBoot绿叶显示yml和端口问题及解决方法

    2023-12-09 00:29:13
  • MyBatis一对多嵌套查询的完整实例

    2023-07-12 02:49:56
  • java 中sendredirect()和forward()方法的区别

    2021-11-07 18:39:28
  • Java PreparedStatement用法详解

    2023-08-08 20:20:51
  • Java中joda日期格式化工具的使用示例

    2023-03-01 11:49:10
  • 一文搞懂Java创建线程的五种方法

    2023-10-30 18:35:04
  • 解决Spring Cloud Feign 请求时附带请求头的问题

    2022-12-11 04:29:46
  • springboot整合shiro与自定义过滤器的全过程

    2023-11-24 21:02:40
  • Idea防沉迷插件StopCoding的安装使用教程

    2023-11-23 07:29:32
  • java(包括springboot)读取resources下文件方式实现

    2021-06-03 20:16:06
  • Spring @Bean注解的使用场景与案例实现

    2023-11-20 04:44:22
  • Java8新特性之空指针异常的克星Optional类的实现

    2023-08-04 18:59:53
  • 详解tryAcquire()、addWaiter()、acquireQueued()

    2022-07-30 10:24:05
  • Java ShutdownHook原理详解

    2023-11-10 21:30:36
  • asp之家 软件编程 m.aspxhome.com