【Spring】DynamicDataSourceHolder 动态数据源切换
一个开发系统需要用多个数据库怎么办呢?可不可以连接多个数据库?
当然可以!随意切换。
DynamicDataSourceHolder
通常是用于动态数据源切换的一个工具类,特别是在多数据源场景下使用。它通过 ThreadLocal
来保存当前线程的数据源标识,以便在同一线程中能够动态切换不同的数据源。
常见场景
在实际项目中,可能需要根据不同的业务需求在多个数据源之间进行切换。比如,读写分离(主从库)、多租户架构等。通过DynamicDataSourceHolder
可以方便地实现这一需求。
常见工具
有许多现有的动态数据源管理工具和框架,可以帮助简化多数据源配置和管理。以下是一些常用的工具和框架:
-
Spring Boot DataSource Routing with AbstractRoutingDataSource
Spring本身提供了AbstractRoutingDataSource
类,可以用于实现动态数据源路由。 -
Spring Cloud DataSource Routing
在Spring Cloud环境中,可以使用Spring Cloud提供的配置和工具实现动态数据源管理。 -
MyBatis Dynamic Datasource
MyBatis提供了一个动态数据源插件mybatis-spring,可以用于在MyBatis中实现动态数据源切换。 -
Dynamic Datasource for Spring Boot
动态数据源管理库dynamic-datasource-spring-boot-starter
,是一个简单易用的Spring Boot动态数据源启动器,支持多种数据源配置和切换。 -
Druid Dynamic Datasource
阿里巴巴的Druid数据源也支持动态数据源切换,可以通过配置Druid的相关属性实现。
下面详细介绍一下 Spring本身提供的 AbstractRoutingDataSource
、SpringBoot 动态数据启动器、以及现在常用的阿里巴巴的Druid数据源。
一、AbstractRoutingDataSource
1.1、 定义 DynamicDataSourceHolder
public class DynamicDataSourceHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
/**
* 设置数据源
* @param dataSourceKey 数据源标识
*/
public static void setDataSource(String dataSourceKey) {
contextHolder.set(dataSourceKey);
}
/**
* 获取数据源
* @return 数据源标识
*/
public static String getDataSource() {
return contextHolder.get();
}
/**
* 清除数据源
*/
public static void clearDataSource() {
contextHolder.remove();
}
}
1.2、 配置动态数据源
需要配置一个 AbstractRoutingDataSource
来实现动态数据源的路由。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSource();
}
}
1.3、 在Spring配置中定义数据源
在Spring配置中定义数据源,并将动态数据源配置为主数据源。
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Bean(name = "dataSource1")
@ConfigurationProperties(prefix = "spring.datasource.ds1")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dataSource2")
@ConfigurationProperties(prefix = "spring.datasource.ds2")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "dataSource")
public DataSource dataSource(
@Qualifier("dataSource1") DataSource dataSource1,
@Qualifier("dataSource2") DataSource dataSource2) {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("ds1", dataSource1);
targetDataSources.put("ds2", dataSource2);
dynamicRoutingDataSource.setTargetDataSources(targetDataSources);
dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource1);
return dynamicRoutingDataSource;
}
}
1.4、在业务代码中切换数据源
在需要切换数据源的业务逻辑中使用 DynamicDataSourceHolder
来设置当前数据源。
@Service
public class SomeService {
@Transactional
public void someMethod() {
// 切换到数据源 ds1
DynamicDataSourceHolder.setDataSource("ds1");
// 执行与数据源 ds1 相关的操作
// 切换到数据源 ds2
DynamicDataSourceHolder.setDataSource("ds2");
// 执行与数据源 ds2 相关的操作
// 恢复到默认数据源
DynamicDataSourceHolder.clearDataSource();
}
}
DynamicDataSourceHolder
通过 ThreadLocal
实现了线程级别的数据源切换,结合 AbstractRoutingDataSource
实现动态数据源路由,可以方便地在多数据源场景下进行数据源的动态切换。需要注意的是,在使用动态数据源时,要确保在合适的时机清除线程本地变量,以防止数据源混乱。
二、Dynamic Datasource for Spring Boot
2.1. 添加依赖
在pom.xml中添加dynamic-datasource-spring-boot-starter依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
2.2. 配置数据源
在application.yml或application.properties中配置多个数据源:
spring:
datasource:
dynamic:
primary: master
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
2.3. 使用注解切换数据源
使用@DS
注解在需要的方法或类上指定数据源:
import com.baomidou.dynamic.datasource.annotation.DS;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@DS("slave")
@Transactional
public void getSlaveData() {
// 逻辑使用从库数据源
}
@DS("master")
@Transactional
public void getMasterData() {
// 逻辑使用主库数据源
}
}
2.4. 启动类配置
在Spring Boot启动类中启用动态数据源支持:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
三、阿里巴巴的Druid
阿里巴巴的Druid 是一个高效、稳定且功能强大的数据库连接池,支持监控、日志输出和多种数据库管理功能。Druid也支持动态数据源切换,通过配置Druid的相关属性和使用Spring的AbstractRoutingDataSource
来实现。
3.1. 引入依赖
在你的Spring Boot项目中引入Druid依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
3.2. 配置多数据源
在 application.yml
中配置多个数据源。Druid支持的数据源配置属性非常丰富,可以配置连接池大小、超时设置、日志等。
spring:
datasource:
dynamic:
primary: master
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/master_db
username: root
password: root
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall,log4j
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/slave_db
username: root
password: root
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall,log4j
3.3. 实现动态数据源路由
通过继承Spring的 AbstractRoutingDataSource
实现动态数据源路由逻辑。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
3.4. 创建数据源上下文持有者
使用 ThreadLocal
来保存当前线程的数据源标识。
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
CONTEXT_HOLDER.set(key);
}
public static String getDataSourceKey() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceKey() {
CONTEXT_HOLDER.remove();
}
}
3.5. 配置数据源
在Spring配置类中配置Druid数据源和动态数据源路由。
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.dynamic.datasource.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.dynamic.datasource.slave")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Primary
@Bean
public DataSource dataSource(
@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave", slaveDataSource);
dynamicRoutingDataSource.setTargetDataSources(targetDataSources);
dynamicRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
return dynamicRoutingDataSource;
}
}
3.6. 使用动态数据源
在需要切换数据源的业务逻辑中使用 DynamicDataSourceContextHolder
来设置当前数据源。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional
public void useMaster() {
DynamicDataSourceContextHolder.setDataSourceKey("master");
// 使用主库执行操作
DynamicDataSourceContextHolder.clearDataSourceKey();
}
@Transactional
public void useSlave() {
DynamicDataSourceContextHolder.setDataSourceKey("slave");
// 使用从库执行操作
DynamicDataSourceContextHolder.clearDataSourceKey();
}
}
总结
Druid Dynamic Datasource通过结合Spring的AbstractRoutingDataSource和ThreadLocal,实现了高效的动态数据源切换。配置和使用都相对简单,并且具有Druid连接池的强大功能,如监控、性能优化等。适用于多数据源、读写分离、多租户等复杂场景。