Spring Boot多数据源动态切换:AbstractRoutingDataSource实战
引言
在企业级应用开发中,随着业务规模的扩大,我们经常会遇到需要同时操作多个数据库的场景:可能是主从读写分离、多租户架构、分库分表需求,或是需要同时连接业务库和日志库等特殊场景。传统的单数据源配置已无法满足这类需求,而Spring Boot提供的AbstractRoutingDataSource
正是解决这类问题的利器。本文将深入探讨如何基于AbstractRoutingDataSource
实现多数据源的动态切换,并通过实战代码演示具体实现方案。
一、核心原理剖析
1.1 AbstractRoutingDataSource工作机制
AbstractRoutingDataSource
是Spring框架提供的一个抽象类,通过路由机制(Routing)实现数据源的动态切换。其核心逻辑是维护一个Map<Object, DataSource>
结构,通过determineCurrentLookupKey()
方法返回当前线程需要使用的数据源标识(lookup key),进而从目标数据源集合中获取对应的DataSource
。
1.2 线程级数据源管理
动态数据源切换的核心是线程隔离。每个请求线程通过ThreadLocal
保存当前数据源标识,确保不同线程间的数据源选择互不干扰。这种设计完美适配Web应用的请求-响应模型,天然支持高并发场景。
二、实战:四步实现动态数据源
2.1 环境准备
在pom.xml
中添加基础依赖:
xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
2.2 数据源配置
在application.yml
中配置主从数据源:
yaml
datasource:master:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/master_dbusername: rootpassword: master_pwdpool-name: MASTER_POOLslave:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/slave_dbusername: rootpassword: slave_pwdpool-name: SLAVE_POOL
2.3 动态数据源核心实现
2.3.1 数据源上下文持有器
java
public class DataSourceContextHolder {private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();public static void setDataSource(String name) {CONTEXT.set(name);}public static String getDataSource() {return CONTEXT.get();}public static void clear() {CONTEXT.remove();}
}
2.3.2 动态数据源实现类
java
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSource();}@Bean@ConfigurationProperties(prefix = "datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties(prefix = "datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}@Beanpublic DataSource dynamicDataSource() {Map<Object, Object> dataSources = new HashMap<>(2);dataSources.put("master", masterDataSource());dataSources.put("slave", slaveDataSource());DynamicDataSource ds = new DynamicDataSource();ds.setDefaultTargetDataSource(masterDataSource());ds.setTargetDataSources(dataSources);return ds;}
}
2.4 切换切面实现
java
@Aspect
@Component
public class DataSourceAspect {@Before("@annotation(com.example.annotation.Master)")public void switchMaster() {DataSourceContextHolder.setDataSource("master");}@Before("@annotation(com.example.annotation.Slave)")public void switchSlave() {DataSourceContextHolder.setDataSource("slave");}@AfterReturning("@annotation(com.example.annotation.Master) || @annotation(com.example.annotation.Slave)")public void clearDataSource() {DataSourceContextHolder.clear();}
}
三、进阶功能扩展
3.1 动态数据源注册
java
public void addNewDataSource(String key, DataSource dataSource) {Map<Object, Object> targetDataSources = new HashMap<>(dynamicDataSource.getTargetDataSources());targetDataSources.put(key, dataSource);dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.afterPropertiesSet();
}
3.2 事务管理增强
java
@Bean
public PlatformTransactionManager transactionManager() {return new DataSourceTransactionManager(dynamicDataSource());
}
四、测试验证
java
@SpringBootTest
class DynamicDSTest {@Autowiredprivate JdbcTemplate jdbcTemplate;@Test@Mastervoid testMasterWrite() {jdbcTemplate.execute("INSERT INTO user(name) VALUES('test')");}@Test@Slavevoid testSlaveRead() {List<Map<String, Object>> result = jdbcTemplate.queryForList("SELECT * FROM user");System.out.println(result);}
}
五、注意事项与优化建议
- 连接池监控:建议集成Druid的监控功能,实时观察各数据源状态
- 失败回退机制:当目标数据源不可用时,自动切换到默认数据源
- 性能调优:根据实际场景调整各连接池参数(maxPoolSize、minIdle等)
- 分布式事务:对于跨数据源事务,建议使用Seata等分布式事务解决方案
- 动态刷新:结合Nacos配置中心实现数据源配置的热更新
总结
通过AbstractRoutingDataSource
实现多数据源动态切换,我们既保持了Spring Boot简洁的配置风格,又获得了灵活的数据源管理能力。这种方案在中小型项目中表现优异,但对于需要复杂分片策略的大型分布式系统,建议考虑集成ShardingSphere等专业中间件。希望本文能为您在应对复杂数据源场景时提供有价值的参考。