在实际开发中,我们经常会遇到需要动态切换数据源的场景,比如多租户系统、读写分离、分库分表等。Spring Boot 提供了灵活的配置方式,结合 AbstractRoutingDataSource
可以轻松实现动态数据源切换。本文将带你一步步实现 Spring Boot 动态数据源的配置与使用。
一、动态数据源的原理
Spring Boot 的动态数据源核心原理是通过 AbstractRoutingDataSource
实现数据源的路由。AbstractRoutingDataSource
是一个抽象类,它允许我们根据当前的上下文(如线程局部变量)动态选择目标数据源。
核心流程:
- 定义多个数据源(如主库、从库)。
- 继承
AbstractRoutingDataSource
,实现determineCurrentLookupKey()
方法,返回当前线程需要使用的数据源标识。 - 通过 AOP 或手动切换的方式,动态设置数据源标识。
二、实现步骤
1. 创建 Spring Boot 项目
使用 Spring Initializr 创建一个 Spring Boot 项目,添加以下依赖:
- Spring Web
- Spring Data JPA
- MySQL Driver
- HikariCP(连接池)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
2. 配置多数据源
在 application.yml
中配置多个数据源,例如主库和从库:
spring:
datasource:
master:
jdbc-url: jdbc:mysql://localhost:3306/master_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
jdbc-url: jdbc:mysql://localhost:3306/slave_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
3. 创建数据源配置类
通过 Java 配置类加载多个数据源。
@Configuration
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
}
4. 实现动态数据源路由
创建 DynamicDataSource
类继承 AbstractRoutingDataSource
,并实现 determineCurrentLookupKey()
方法。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceKey();
}
}
5. 创建数据源上下文管理类
使用 ThreadLocal
保存当前线程的数据源标识。
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
6. 配置动态数据源
将多个数据源注入到 DynamicDataSource
中。
@Configuration
public class DynamicDataSourceConfig {
@Bean
public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave", slaveDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(masterDataSource); // 默认数据源
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
7. 配置事务管理器
由于动态数据源的切换会影响事务管理,需要配置一个支持动态数据源的事务管理器。
@Bean
public PlatformTransactionManager transactionManager(DataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
8. 使用 AOP 动态切换数据源
通过 AOP 在方法执行前切换数据源。
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(com.example.demo.annotation.Master)")
public void setMasterDataSource() {
DataSourceContextHolder.setDataSourceKey("master");
}
@Before("@annotation(com.example.demo.annotation.Slave)")
public void setSlaveDataSource() {
DataSourceContextHolder.setDataSourceKey("slave");
}
@After("@annotation(com.example.demo.annotation.Master) || @annotation(com.example.demo.annotation.Slave)")
public void clearDataSource() {
DataSourceContextHolder.clearDataSourceKey();
}
}
9. 定义注解
为了方便切换数据源,定义两个注解:@Master
和 @Slave
。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Master {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Slave {
}
10. 测试动态数据源
在 Service 层使用注解切换数据源。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Master
public void addUser(User user) {
userRepository.save(user);
}
@Slave
public List<User> getUsers() {
return userRepository.findAll();
}
}
三、总结
通过以上步骤,我们实现了一个简单的 Spring Boot 动态数据源切换功能。核心点包括:
- 使用
AbstractRoutingDataSource
实现数据源路由。 - 通过
ThreadLocal
管理当前线程的数据源标识。 - 使用 AOP 和注解简化数据源切换。
动态数据源切换是一个非常实用的功能,适用于多租户、读写分离等场景。希望本文能帮助你更好地理解和应用 Spring Boot 动态数据源技术!