Spring Boot 如何配置多数据源?
Spring Boot 默认只配置一个数据源。要配置多数据源(例如一个主库、一个从库,或者连接不同业务的数据库),通常有两种主流方案:
- 原生手动配置(分包隔离):通过配置类手动创建多个
DataSource,并根据包路径(Package)将不同的 Mapper/Repository 映射到不同的数据源。 - 使用第三方组件(动态数据源):使用如
dynamic-datasource-spring-boot-starter,通过注解切换数据源。
方案一:原生手动配置(以 MyBatis 为例)
这是最标准的方式,原理是将不同的 Mapper 接口放在不同的包下,每个包使用独立的 SqlSessionFactory 和 DataSource。
1. 配置文件 (application.yml)
我们需要禁用默认的数据源自动配置,并定义两组数据源配置。
yaml
spring:
datasource:
# 主数据源配置
primary:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db_primary
username: root
password: password
# 第二数据源配置
secondary:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db_secondary
username: root
password: password
# MyBatis Mapper 路径配置 (如果使用 XML)
mybatis:
mapper-locations: classpath:mapper/**/*.xml
2. 配置数据源 Bean
创建一个配置类,读取配置文件并生成 DataSource 对象。
java
@Configuration
public class DataSourceConfig {
@Primary // 必须指定一个主数据源,供 Spring 上下文默认使用
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
3. 配置 MyBatis (分包扫描)
这是核心步骤。你需要为每个数据源创建一个配置类,指定该数据源负责扫描哪个包下的 Mapper。
主数据源配置类 (PrimaryMyBatisConfig.java):
java
@Configuration
@MapperScan(
basePackages = "com.example.project.mapper.primary", // 扫描主库 Mapper 的包路径
sqlSessionFactoryRef = "primarySqlSessionFactory"
)
public class PrimaryMyBatisConfig {
@Resource(name = "primaryDataSource")
private DataSource primaryDataSource;
@Primary
@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(primaryDataSource);
// 如果有 XML 映射文件,需要在这里指定
// bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/primary/*.xml"));
return bean.getObject();
}
@Primary
@Bean(name = "primaryTransactionManager")
public DataSourceTransactionManager primaryTransactionManager() {
return new DataSourceTransactionManager(primaryDataSource);
}
}
第二数据源配置类 (SecondaryMyBatisConfig.java):
java
@Configuration
@MapperScan(
basePackages = "com.example.project.mapper.secondary", // 扫描从库 Mapper 的包路径
sqlSessionFactoryRef = "secondarySqlSessionFactory"
)
public class SecondaryMyBatisConfig {
@Resource(name = "secondaryDataSource")
private DataSource secondaryDataSource;
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(secondaryDataSource);
return bean.getObject();
}
@Bean(name = "secondaryTransactionManager")
public DataSourceTransactionManager secondaryTransactionManager() {
return new DataSourceTransactionManager(secondaryDataSource);
}
}
4. 使用
- 将操作主库的 Mapper 接口放在
com.example.project.mapper.primary包下。 - 将操作从库的 Mapper 接口放在
com.example.project.mapper.secondary包下。 - Spring Boot 启动时会自动根据包路径绑定对应的数据源。
方案二:使用第三方组件(推荐:MyBatis-Plus Dynamic Datasource)
如果你觉得手动配置太繁琐,或者需要更灵活的切换(例如在同一个 Service 方法中切换),强烈推荐使用 dynamic-datasource-spring-boot-starter(由 MyBatis-Plus 团队维护)。
1. 引入依赖
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.3.0</version> <!-- 请检查最新版本 -->
</dependency>
2. 配置文件 (application.yml)
结构非常清晰,支持分组。
yaml
spring:
datasource:
dynamic:
# 设置默认数据源
primary: master
# 严格模式,匹配不到数据源报错
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/db_master
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
slave_1:
url: jdbc:mysql://localhost:3306/db_slave
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
3. 使用注解切换
直接在 Service 层或 Mapper 层使用 @DS 注解即可。
java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 不加注解,默认使用 primary 配置的数据源 (master)
public void addUser(User user) {
userMapper.insert(user);
}
// 切换到 slave_1 数据源
@DS("slave_1")
public List<User> getAllUsers() {
return userMapper.selectList(null);
}
}
方案对比与总结
| 特性 | 方案一:原生手动配置 (分包) | 方案二:Dynamic Datasource (注解) |
|---|---|---|
| 复杂度 | 高,需要写多个 Config 类 | 低,引入依赖改 YAML 即可 |
| 灵活性 | 较低,绑定在包路径上 | 高,可精确到方法级或类级 |
| 事务处理 | 只能处理单库事务,跨库需 JTA | 支持多数据源切换,但跨库事务仍需注意 |
| 适用场景 | 业务模块完全独立,数据库物理隔离 | 读写分离、多租户系统、混合业务 |
建议:
- 如果是新项目且追求开发效率,首选 方案二。
- 如果是老项目改造或者对第三方库有严格限制,使用 方案一。
注意事项:事务问题
无论哪种方案,@Transactional 默认只能管理一个数据源的事务。
- 在方案一中,你需要指定事务管理器:
@Transactional(transactionManager = "secondaryTransactionManager")。 - 如果一个方法同时操作两个数据库,普通的
@Transactional无法保证原子性(即无法同时回滚两个库),这涉及到分布式事务(如使用 Seata 或 Atomikos)。