一、多数据源应用场景分析
在分布式系统架构中,多数据源配置是常见需求:
- 读写分离:主库处理写操作,从库处理读请求
- 多租户架构:不同租户使用独立数据库实例
- 数据迁移:新旧系统并行运行时的数据双写
- 微服务隔离:每个服务拥有独立数据库权限
传统静态配置方式存在显著缺陷:需重启应用才能切换数据源,无法满足动态调整需求。Spring框架提供的AbstractRoutingDataSource抽象类,为动态数据源路由提供了标准实现路径。
二、核心组件实现方案
2.1 数据源常量定义
创建常量类规范数据源标识,避免硬编码问题:
package com.example.datasource;public final class DataSourceConstants {// 主数据源标识public static final String MASTER = "master";// 从数据源标识集合public static final String SLAVE_1 = "slave1";public static final String SLAVE_2 = "slave2";// 禁止实例化private DataSourceConstants() {throw new IllegalStateException("Utility class");}}
2.2 上下文管理器实现
采用ThreadLocal实现线程安全的数据源上下文存储:
package com.example.datasource;public class DataSourceContextHolder {private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();// 设置当前线程数据源public static void setDataSource(String dataSource) {CONTEXT_HOLDER.set(dataSource);}// 获取当前线程数据源public static String getDataSource() {return CONTEXT_HOLDER.get();}// 清除上下文(防止内存泄漏)public static void clear() {CONTEXT_HOLDER.remove();}}
2.3 动态路由数据源
继承AbstractRoutingDataSource实现动态路由逻辑:
package com.example.datasource.config;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSource();}}
三、完整配置实现
3.1 数据源配置类
@Configurationpublic class DataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.slave1")public DataSource slave1DataSource() {return DataSourceBuilder.create().build();}@Beanpublic DataSource dynamicDataSource() {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceConstants.MASTER, masterDataSource());targetDataSources.put(DataSourceConstants.SLAVE_1, slave1DataSource());DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(masterDataSource());return dynamicDataSource;}}
3.2 事务管理器配置
@Configuration@EnableTransactionManagementpublic class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(DataSource dynamicDataSource) {return new DataSourceTransactionManager(dynamicDataSource);}}
3.3 MyBatis集成配置
@Configuration@MapperScan(basePackages = "com.example.mapper")public class MyBatisConfig {@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dynamicDataSource) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dynamicDataSource);// 其他配置...return sessionFactory.getObject();}}
四、高级应用技巧
4.1 基于注解的路由控制
创建自定义注解实现声明式路由:
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface DataSource {String value() default DataSourceConstants.MASTER;}
实现切面进行路由控制:
@Aspect@Componentpublic class DataSourceAspect {@Before("@annotation(dataSource)")public void beforeSwitchDataSource(JoinPoint point, DataSource dataSource) {DataSourceContextHolder.setDataSource(dataSource.value());}@After("@annotation(dataSource)")public void afterSwitchDataSource(JoinPoint point, DataSource dataSource) {DataSourceContextHolder.clear();}}
4.2 负载均衡策略实现
针对多从库场景实现轮询算法:
public class LoadBalanceDataSourceContextHolder {private static final AtomicInteger counter = new AtomicInteger(0);private static final String[] SLAVE_DATA_SOURCES = {DataSourceConstants.SLAVE_1,DataSourceConstants.SLAVE_2};public static void useSlave() {String dataSource = SLAVE_DATA_SOURCES[counter.getAndIncrement() % SLAVE_DATA_SOURCES.length];DataSourceContextHolder.setDataSource(dataSource);}}
4.3 动态数据源扩展
支持运行时新增数据源:
@Servicepublic class DataSourceRegistry {@Autowiredprivate DynamicDataSource dynamicDataSource;public void registerDataSource(String name, DataSource dataSource) {Map<Object, Object> targetDataSources = new HashMap<>(dynamicDataSource.getTargetDataSources());targetDataSources.put(name, dataSource);dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.afterPropertiesSet(); // 刷新数据源}}
五、最佳实践建议
- 连接池配置:建议使用HikariCP等高性能连接池,合理设置连接数参数
- 异常处理:捕获SQLException时需清理ThreadLocal上下文
- 监控告警:集成日志服务记录数据源切换事件
- 性能测试:多数据源场景下需重点测试连接获取性能
- 资源释放:在Web请求结束时清除上下文(可通过Filter实现)
六、常见问题解决方案
- 事务失效问题:确保@Transactional注解使用在public方法上
- 线程安全问题:避免在异步任务中直接使用ThreadLocal
- 动态刷新问题:修改配置后需调用afterPropertiesSet()刷新
- 类型转换异常:确保数据源标识与配置文件中的key完全匹配
通过上述技术方案,开发者可以构建出灵活、可靠的动态多数据源系统。实际项目中建议结合具体业务场景进行适当调整,例如在读写分离场景中可集成数据库中间件实现更复杂的路由策略。对于超大规模系统,可考虑将数据源管理模块拆分为独立服务,通过RPC调用实现分布式环境下的数据源控制。