Java如何实现多数据源切换
在Java中实现多数据源切换通常用于需要动态切换不同数据库(如主从库、分库分表、多租户等)的场景。以下是常见的实现方案,基于Spring/Spring Boot框架:
1. 方案概览
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| AbstractRoutingDataSource | 简单动态切换(如主从库) | 轻量级,Spring原生支持 | 需手动管理连接,不支持复杂事务 |
| MyBatis插件 | MyBatis项目,需拦截SQL选择数据源 | 灵活,与ORM深度集成 | 需熟悉MyBatis拦截器机制 |
| ShardingSphere | 分库分表、读写分离等复杂场景 | 功能强大,支持分布式事务 | 学习成本较高 |
| JPA+Hibernate | 使用JPA的项目 | 与JPA无缝集成 | 配置复杂 |
2. 具体实现(以AbstractRoutingDataSource为例)
步骤1:定义数据源枚举与上下文持有器
// 数据源类型枚举
public enum DataSourceType {
MASTER, SLAVE
}
// 使用ThreadLocal保存当前线程的数据源标识
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<DataSourceType> CONTEXT = new ThreadLocal<>();
public static void setDataSourceType(DataSourceType type) {
CONTEXT.set(type);
}
public static DataSourceType getDataSourceType() {
return CONTEXT.get() == null ? DataSourceType.MASTER : CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
步骤2:继承AbstractRoutingDataSource实现动态路由
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
步骤3:配置多数据源Bean
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource dynamicDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER, masterDataSource());
targetDataSources.put(DataSourceType.SLAVE, slaveDataSource());
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
}
步骤4:配置application.yml
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
步骤5:使用AOP或注解切换数据源
-
自定义注解:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { DataSourceType value() default DataSourceType.MASTER; } -
AOP切面:
@Aspect @Component public class DataSourceAspect { @Before("@annotation(dataSource)") public void beforeSwitchDataSource(DataSource dataSource) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value()); } @After("@annotation(dataSource)") public void afterClearDataSource(DataSource dataSource) { DynamicDataSourceContextHolder.clear(); } } -
使用注解切换:
@Service public class UserService { @DataSource(DataSourceType.MASTER) public void addUser(User user) { // 使用主库 } @DataSource(DataSourceType.SLAVE) public User getUserById(Long id) { // 使用从库 return userMapper.selectById(id); } }
3. 高级场景优化
事务管理
- 问题:直接使用
AbstractRoutingDataSource时,事务内无法切换数据源(因为事务会绑定初始数据源)。 - 解决方案:
- 使用
@Transactional(propagation = Propagation.REQUIRES_NEW)开启新事务。 - 结合ShardingSphere或Atomikos实现分布式事务。
- 使用
多租户支持
-
在
DynamicDataSourceContextHolder中存储租户ID,动态选择对应租户的数据源:public class TenantDataSourceAspect { @Before("execution(* com.example..*.*(..))") public void setTenantDataSource(JoinPoint joinPoint) { String tenantId = TenantContext.getCurrentTenant(); DynamicDataSourceContextHolder.setDataSourceType(tenantId); } }
4. 其他方案对比
MyBatis插件实现
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyBatisDataSourceInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
if (ms.getSqlCommandType() == SqlCommandType.SELECT) {
DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);
} else {
DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);
}
return invocation.proceed();
}
}
ShardingSphere配置
spring:
shardingsphere:
datasource:
names: master,slave
master:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://localhost:3306/master_db
username: root
password: 123456
slave:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://localhost:3306/slave_db
username: root
password: 123456
masterslave:
load-balance-algorithm-type: round_robin
name: ms_ha
master-data-source-name: master
slave-data-source-names: slave
5. 总结
- 简单场景:
AbstractRoutingDataSource+ 注解/AOP。 - 复杂场景:ShardingSphere(支持分库分表、读写分离、分布式事务)。
- 关键点:
- 使用
ThreadLocal保证线程安全。 - 注意事务内数据源切换的局限性。
- 生产环境建议配合连接池(如HikariCP)使用。
- 使用


浙公网安备 33010602011771号