目录

3.3-Spring-Boot多数据源动态切换AbstractRoutingDataSource实战

3.3 Spring Boot多数据源动态切换:AbstractRoutingDataSource实战

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.Driver
    jdbc-url: jdbc:mysql://localhost:3306/master_db
    username: root
    password: master_pwd
    pool-name: MASTER_POOL
  slave:
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://localhost:3306/slave_db
    username: root
    password: slave_pwd
    pool-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 {

    @Override
    protected 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();
    }

    @Bean
    public 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 {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    @Master
    void testMasterWrite() {
        jdbcTemplate.execute("INSERT INTO user(name) VALUES('test')");
    }

    @Test
    @Slave
    void testSlaveRead() {
        List<Map<String, Object>> result = 
            jdbcTemplate.queryForList("SELECT * FROM user");
        System.out.println(result);
    }
}

五、注意事项与优化建议

  1. 连接池监控 :建议集成Druid的监控功能,实时观察各数据源状态
  2. 失败回退机制 :当目标数据源不可用时,自动切换到默认数据源
  3. 性能调优 :根据实际场景调整各连接池参数(maxPoolSize、minIdle等)
  4. 分布式事务 :对于跨数据源事务,建议使用Seata等分布式事务解决方案
  5. 动态刷新 :结合Nacos配置中心实现数据源配置的热更新

总结

通过 AbstractRoutingDataSource 实现多数据源动态切换,我们既保持了Spring Boot简洁的配置风格,又获得了灵活的数据源管理能力。这种方案在中小型项目中表现优异,但对于需要复杂分片策略的大型分布式系统,建议考虑集成ShardingSphere等专业中间件。希望本文能为您在应对复杂数据源场景时提供有价值的参考。