Spring框架下动态多数据源配置与实现指南

一、多数据源应用场景分析

在分布式系统架构中,多数据源配置是常见需求:

  1. 读写分离:主库处理写操作,从库处理读请求
  2. 多租户架构:不同租户使用独立数据库实例
  3. 数据迁移:新旧系统并行运行时的数据双写
  4. 微服务隔离:每个服务拥有独立数据库权限

传统静态配置方式存在显著缺陷:需重启应用才能切换数据源,无法满足动态调整需求。Spring框架提供的AbstractRoutingDataSource抽象类,为动态数据源路由提供了标准实现路径。

二、核心组件实现方案

2.1 数据源常量定义

创建常量类规范数据源标识,避免硬编码问题:

  1. package com.example.datasource;
  2. public final class DataSourceConstants {
  3. // 主数据源标识
  4. public static final String MASTER = "master";
  5. // 从数据源标识集合
  6. public static final String SLAVE_1 = "slave1";
  7. public static final String SLAVE_2 = "slave2";
  8. // 禁止实例化
  9. private DataSourceConstants() {
  10. throw new IllegalStateException("Utility class");
  11. }
  12. }

2.2 上下文管理器实现

采用ThreadLocal实现线程安全的数据源上下文存储:

  1. package com.example.datasource;
  2. public class DataSourceContextHolder {
  3. private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
  4. // 设置当前线程数据源
  5. public static void setDataSource(String dataSource) {
  6. CONTEXT_HOLDER.set(dataSource);
  7. }
  8. // 获取当前线程数据源
  9. public static String getDataSource() {
  10. return CONTEXT_HOLDER.get();
  11. }
  12. // 清除上下文(防止内存泄漏)
  13. public static void clear() {
  14. CONTEXT_HOLDER.remove();
  15. }
  16. }

2.3 动态路由数据源

继承AbstractRoutingDataSource实现动态路由逻辑:

  1. package com.example.datasource.config;
  2. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
  3. public class DynamicDataSource extends AbstractRoutingDataSource {
  4. @Override
  5. protected Object determineCurrentLookupKey() {
  6. return DataSourceContextHolder.getDataSource();
  7. }
  8. }

三、完整配置实现

3.1 数据源配置类

  1. @Configuration
  2. public class DataSourceConfig {
  3. @Bean
  4. @ConfigurationProperties("spring.datasource.master")
  5. public DataSource masterDataSource() {
  6. return DataSourceBuilder.create().build();
  7. }
  8. @Bean
  9. @ConfigurationProperties("spring.datasource.slave1")
  10. public DataSource slave1DataSource() {
  11. return DataSourceBuilder.create().build();
  12. }
  13. @Bean
  14. public DataSource dynamicDataSource() {
  15. Map<Object, Object> targetDataSources = new HashMap<>();
  16. targetDataSources.put(DataSourceConstants.MASTER, masterDataSource());
  17. targetDataSources.put(DataSourceConstants.SLAVE_1, slave1DataSource());
  18. DynamicDataSource dynamicDataSource = new DynamicDataSource();
  19. dynamicDataSource.setTargetDataSources(targetDataSources);
  20. dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
  21. return dynamicDataSource;
  22. }
  23. }

3.2 事务管理器配置

  1. @Configuration
  2. @EnableTransactionManagement
  3. public class TransactionConfig {
  4. @Bean
  5. public PlatformTransactionManager transactionManager(DataSource dynamicDataSource) {
  6. return new DataSourceTransactionManager(dynamicDataSource);
  7. }
  8. }

3.3 MyBatis集成配置

  1. @Configuration
  2. @MapperScan(basePackages = "com.example.mapper")
  3. public class MyBatisConfig {
  4. @Bean
  5. public SqlSessionFactory sqlSessionFactory(DataSource dynamicDataSource) throws Exception {
  6. SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
  7. sessionFactory.setDataSource(dynamicDataSource);
  8. // 其他配置...
  9. return sessionFactory.getObject();
  10. }
  11. }

四、高级应用技巧

4.1 基于注解的路由控制

创建自定义注解实现声明式路由:

  1. @Target({ElementType.METHOD, ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface DataSource {
  4. String value() default DataSourceConstants.MASTER;
  5. }

实现切面进行路由控制:

  1. @Aspect
  2. @Component
  3. public class DataSourceAspect {
  4. @Before("@annotation(dataSource)")
  5. public void beforeSwitchDataSource(JoinPoint point, DataSource dataSource) {
  6. DataSourceContextHolder.setDataSource(dataSource.value());
  7. }
  8. @After("@annotation(dataSource)")
  9. public void afterSwitchDataSource(JoinPoint point, DataSource dataSource) {
  10. DataSourceContextHolder.clear();
  11. }
  12. }

4.2 负载均衡策略实现

针对多从库场景实现轮询算法:

  1. public class LoadBalanceDataSourceContextHolder {
  2. private static final AtomicInteger counter = new AtomicInteger(0);
  3. private static final String[] SLAVE_DATA_SOURCES = {
  4. DataSourceConstants.SLAVE_1,
  5. DataSourceConstants.SLAVE_2
  6. };
  7. public static void useSlave() {
  8. String dataSource = SLAVE_DATA_SOURCES[counter.getAndIncrement() % SLAVE_DATA_SOURCES.length];
  9. DataSourceContextHolder.setDataSource(dataSource);
  10. }
  11. }

4.3 动态数据源扩展

支持运行时新增数据源:

  1. @Service
  2. public class DataSourceRegistry {
  3. @Autowired
  4. private DynamicDataSource dynamicDataSource;
  5. public void registerDataSource(String name, DataSource dataSource) {
  6. Map<Object, Object> targetDataSources = new HashMap<>(dynamicDataSource.getTargetDataSources());
  7. targetDataSources.put(name, dataSource);
  8. dynamicDataSource.setTargetDataSources(targetDataSources);
  9. dynamicDataSource.afterPropertiesSet(); // 刷新数据源
  10. }
  11. }

五、最佳实践建议

  1. 连接池配置:建议使用HikariCP等高性能连接池,合理设置连接数参数
  2. 异常处理:捕获SQLException时需清理ThreadLocal上下文
  3. 监控告警:集成日志服务记录数据源切换事件
  4. 性能测试:多数据源场景下需重点测试连接获取性能
  5. 资源释放:在Web请求结束时清除上下文(可通过Filter实现)

六、常见问题解决方案

  1. 事务失效问题:确保@Transactional注解使用在public方法上
  2. 线程安全问题:避免在异步任务中直接使用ThreadLocal
  3. 动态刷新问题:修改配置后需调用afterPropertiesSet()刷新
  4. 类型转换异常:确保数据源标识与配置文件中的key完全匹配

通过上述技术方案,开发者可以构建出灵活、可靠的动态多数据源系统。实际项目中建议结合具体业务场景进行适当调整,例如在读写分离场景中可集成数据库中间件实现更复杂的路由策略。对于超大规模系统,可考虑将数据源管理模块拆分为独立服务,通过RPC调用实现分布式环境下的数据源控制。