多数据源

返回 Spring Boot 基础

多数据源指在同一应用中同时连接多个数据库,常见场景:业务库 + 日志库、主库 + 从库(读写分离)、多租户隔离。


方案对比

方案适用场景侵入性
手动配置多个 DataSource Bean数据源固定、少量高(需为每个库配置全套)
AbstractRoutingDataSource动态切换、有限数量中(自定义路由逻辑)
dynamic-datasource-spring-boot-starter通用多数据源低(注解驱动,推荐)
ShardingSphere分库分表 + 读写分离低(配置驱动)

方案一:手动配置多 DataSource

适合两个固定数据源(如主库 + 报表库)。

# application.yml
spring:
  datasource:
    primary:
      url: jdbc:mysql://localhost:3306/main_db
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    report:
      url: jdbc:mysql://localhost:3306/report_db
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
@Configuration
public class DataSourceConfig {
 
    // 主数据源(@Primary 保证默认注入)
    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
 
    // 报表数据源
    @Bean
    @ConfigurationProperties("spring.datasource.report")
    public DataSource reportDataSource() {
        return DataSourceBuilder.create().build();
    }
 
    // 为报表库单独配置 JdbcTemplate
    @Bean
    public JdbcTemplate reportJdbcTemplate(
            @Qualifier("reportDataSource") DataSource ds) {
        return new JdbcTemplate(ds);
    }
}
// Service 中通过 @Qualifier 注入指定库的 JdbcTemplate
@Service
public class ReportService {
 
    private final JdbcTemplate reportJdbc;
 
    public ReportService(
            @Qualifier("reportJdbcTemplate") JdbcTemplate reportJdbc) {
        this.reportJdbc = reportJdbc;
    }
}

多数据源下的 JPA 配置

每个数据源需要独立的 EntityManagerFactoryTransactionManager

@Configuration
@EnableJpaRepositories(
    basePackages = "com.example.report.repo",
    entityManagerFactoryRef = "reportEntityManagerFactory",
    transactionManagerRef = "reportTransactionManager"
)
public class ReportJpaConfig {
 
    @Bean
    public LocalContainerEntityManagerFactoryBean reportEntityManagerFactory(
            @Qualifier("reportDataSource") DataSource ds,
            EntityManagerFactoryBuilder builder) {
        return builder
            .dataSource(ds)
            .packages("com.example.report.entity")
            .build();
    }
 
    @Bean
    public PlatformTransactionManager reportTransactionManager(
            @Qualifier("reportEntityManagerFactory")
            EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}

事务注解需指定对应的 TransactionManager,详见 事务管理

@Transactional("reportTransactionManager")
public void saveReport(Report report) { ... }

方案二:dynamic-datasource-spring-boot-starter(推荐)

baomidou/dynamic-datasource 是目前最主流的多数据源方案,支持注解切换、分组路由、连接池隔离。

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>
spring:
  datasource:
    dynamic:
      primary: master         # 默认数据源
      strict: false           # 未匹配时是否报错
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/main_db
          username: root
          password: root
        slave1:
          url: jdbc:mysql://localhost:3307/main_db
          username: root
          password: root
        report:
          url: jdbc:mysql://localhost:3306/report_db
          username: root
          password: root
// @DS 注解指定数据源,可加在类或方法上,方法优先级高于类
@Service
@DS("master")
public class UserService {
 
    public User findById(Long id) { ... }           // 使用 master
 
    @DS("slave1")
    public List<User> findAll() { ... }             // 使用 slave1
}
 
@Service
@DS("report")
public class ReportService {
    // 所有方法使用 report 数据源
}

方案三:AbstractRoutingDataSource(自定义路由)

适合需要完全控制路由逻辑的场景:

// 1. 线程本地存储当前数据源标识
public class DataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
 
    public static void set(String key) { CONTEXT.set(key); }
    public static String get() { return CONTEXT.get(); }
    public static void clear() { CONTEXT.remove(); }
}
 
// 2. 实现路由逻辑
public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.get();
    }
}
 
// 3. 注册路由数据源
@Configuration
public class RoutingDataSourceConfig {
 
    @Bean
    public DataSource routingDataSource(
            @Qualifier("masterDs") DataSource master,
            @Qualifier("slaveDs") DataSource slave) {
        Map<Object, Object> targets = new HashMap<>();
        targets.put("master", master);
        targets.put("slave", slave);
 
        RoutingDataSource routing = new RoutingDataSource();
        routing.setDefaultTargetDataSource(master);
        routing.setTargetDataSources(targets);
        return routing;
    }
}
 
// 4. 配合 AOP 自动切换(见 读写分离)

详见 读写分离 中基于 AOP 的自动路由实现。


事务注意事项

多数据源下跨库事务无法用单个 @Transactional 保证原子性,需使用分布式事务方案:

方案特点
Seata AT 模式自动补偿,对代码无侵入,性能较好
Seata TCC 模式手动编写 Try/Confirm/Cancel,强一致
本地消息表最终一致,依赖消息队列
避免跨库事务业务设计上拆分,单库内保证事务

同一数据源内的事务保持不变,详见 事务管理


相关链接