1. 为什么需要多数据源支持?
在真实的SpringBoot企业级项目中,单一数据源往往无法满足业务需求。我经历过多个需要同时连接不同数据库的场景:主从读写分离、分库分表、多租户架构、异构数据库整合等。比如最近一个电商项目,需要同时操作MySQL商品库、PostgreSQL用户库和Redis缓存,这就是典型的多数据源场景。
dynamic-datasource这个轻量级组件,完美解决了SpringBoot原生单数据源的局限性。它通过动态路由机制,允许我们在运行时根据需要切换不同数据源。与手动配置多个DataSource相比,它的优势在于:
- 声明式切换:只需在方法或类上添加@DS注解即可指定数据源
- 无侵入性:对原有MyBatis/MyBatis-Plus代码零改造
- 功能完备:支持事务、SpEL表达式、默认主库等企业级特性
- 性能优异:采用动态代理模式,切换开销几乎可以忽略
提示:在微服务架构下,虽然每个服务通常对应一个数据库,但在数据迁移、跨库查询等场景中,多数据源仍然是必备能力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与基础配置
2.1 依赖引入
首先在pom.xml中添加starter依赖(以SpringBoot 2.7.x为例):
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>
注意版本兼容性:
- SpringBoot 1.5.x → 使用2.5.x版本
- SpringBoot 2.x → 3.x版本
- SpringBoot 3.x → 4.x版本
2.2 数据源配置
在application.yml中配置主从数据源:
yaml复制spring:
datasource:
dynamic:
primary: master # 设置默认数据源
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/master_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave1:
url: jdbc:mysql://127.0.0.1:3307/slave_db1
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
oracle_db:
url: jdbc:oracle:thin:@localhost:1521:ORCL
username: scott
password: tiger
driver-class-name: oracle.jdbc.OracleDriver
配置要点:
- 不同数据源类型需要对应正确的driver-class-name
- 主从数据源建议使用相同数据库类型
- 生产环境密码应使用加密配置
3. 核心功能实现详解
3.1 基础用法:@DS注解
在Service层方法上指定数据源:
java复制@Service
public class OrderServiceImpl implements OrderService {
@DS("master") // 默认走主库
public void createOrder(Order order) {
// 主库写入
}
@DS("slave1") // 查询走从库
public Order getOrderById(Long id) {
// 从库查询
}
@DS("oracle_db") // 切换到Oracle数据源
public List<Report> generateReport() {
// 操作Oracle数据库
}
}
3.2 高级特性:SpEL表达式
动态决定数据源:
java复制@DS("#header.datasource") // 从请求头获取数据源名称
public List<User> getUsers(HttpHeaders headers) {
// ...
}
3.3 事务处理
多数据源事务需要特别注意:
java复制@DS("master")
@Transactional // 只对master数据源生效
public void crossDbOperation() {
// 操作master数据源
slaveService.updateSlaveData(); // 操作slave数据源
// 如果slave操作失败,master不会回滚!
}
对于真正需要分布式事务的场景,建议:
- 使用Seata等分布式事务框架
- 或采用最终一致性方案
4. 实战中的坑与解决方案
4.1 多数据源事务失效问题
现象:在@Transactional方法内切换数据源无效。
原因:Spring事务管理器和数据源路由的执行顺序问题。
解决方案:
java复制@Transactional
public void businessMethod() {
// 方法1:编程式指定数据源
DynamicDataSourceContextHolder.push("slave1");
try {
slaveDao.query();
} finally {
DynamicDataSourceContextHolder.poll();
}
// 方法2:使用嵌套Service
slaveService.query();
}
4.2 连接泄漏问题
现象:高并发下出现连接池耗尽。
排查步骤:
- 检查各数据源的HikariCP配置:
yaml复制spring:
datasource:
dynamic:
datasource:
master:
hikari:
maximum-pool-size: 20
leak-detection-threshold: 5000 # 泄漏检测阈值(ms)
- 添加Druid监控:
java复制@Bean
public ServletRegistrationBean<StatViewServlet> druidServlet() {
return new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
}
4.3 MyBatis-Plus兼容问题
现象:分页插件或自动填充功能异常。
解决方案:
java复制@Configuration
public class MyBatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 需要按数据源分别配置
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
5. 性能优化实践
5.1 数据源预热
启动时初始化连接池:
java复制@Slf4j
@Component
public class DataSourceInitializer {
@Resource
private DataSource dataSource;
@PostConstruct
public void init() {
if (dataSource instanceof DynamicRoutingDataSource) {
Map<String, DataSource> dataSources =
((DynamicRoutingDataSource) dataSource).getCurrentDataSources();
dataSources.values().forEach(ds -> {
try (Connection conn = ds.getConnection()) {
log.info("预热数据源连接: {}", conn.getMetaData().getURL());
} catch (SQLException e) {
log.error("数据源预热失败", e);
}
});
}
}
}
5.2 动态增减数据源
运行时动态注册新数据源:
java复制@Autowired
private DynamicDataSourceProvider provider;
public void addNewDataSource(String name, DataSourceProperty property) {
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
DataSource newDataSource = provider.createDataSource(property);
ds.addDataSource(name, newDataSource);
}
5.3 监控指标集成
集成Micrometer监控:
java复制@Configuration
public class MetricsConfig {
@Bean
public DataSourcePoolMetrics dataSourcePoolMetrics(DataSource dataSource) {
return new DataSourcePoolMetrics(
(HikariDataSource) dataSource,
"app_datasource",
Tags.empty()
);
}
}
6. 生产环境最佳实践
- 配置建议:
yaml复制spring:
datasource:
dynamic:
strict: true # 开启严格模式,未指定数据源时抛出异常
seata: false # 不使用seata时务必关闭
hikari:
connection-timeout: 30000
max-lifetime: 1800000
idle-timeout: 600000
- 命名规范:
- 主库:master
- 从库:slave_业务线_序号(如slave_trade_01)
- 其他:业务名_db类型(如report_oracle)
- 灾备方案:
java复制@DS("#{T(com.util.DsSelector).select()}")
public List<Data> getCriticalData() {
// 通过SPEL选择可用数据源
}
在最近的一个千万级用户项目中,我们采用dynamic-datasource实现了:
- 1主3从的MySQL集群
- 2个Oracle报表库
- Redis缓存伪数据源
通过合理的配置和上述优化手段,系统在高峰期保持了<5ms的数据源切换延迟。
