1. 项目概述
在传统SpringBoot项目中,我们通常只需要配置单个数据源即可满足大部分业务需求。但随着业务复杂度提升,多数据源场景变得越来越常见:比如读写分离架构、分库分表设计、跨业务系统数据同步等场景。过去要实现多数据源配置,往往需要手动编写大量样板代码,包括多个DataSource的实例化、事务管理器的配置、MyBatis会话工厂的定制等,整个过程繁琐且容易出错。
dynamic-datasource-spring-boot-starter这个组件完美解决了这个问题。它基于SpringBoot的自动配置机制,通过约定大于配置的原则,让开发者只需简单几步就能实现多数据源的切换和管理。我在最近的一个电商平台项目中就采用了这个方案,成功实现了订单库与用户库的物理隔离,同时保证了事务的一致性。
2. 核心原理剖析
2.1 动态数据源工作原理
这个starter的核心是DynamicRoutingDataSource类,它实现了Spring的AbstractRoutingDataSource。其核心机制是:
- 数据源路由:通过扩展AbstractRoutingDataSource的determineCurrentLookupKey方法,在每次数据库操作前动态决定使用哪个数据源
- 注解驱动:@DS注解通过AOP拦截,将数据源标识存储到ThreadLocal中
- 上下文传递:通过DataSourceContextHolder维护当前线程的数据源选择状态
关键点:数据源切换发生在执行SQL操作之前,而不是在事务开始时。这意味着同一个事务方法内切换数据源可能会导致意外情况。
2.2 事务处理机制
组件通过DataSourceTransactionManager扩展实现了特殊的事务处理:
- 事务同步:当开启事务时,会锁定初始选择的数据源
- 传播行为:PROPAGATION_REQUIRES_NEW会新建独立连接
- 分布式事务:集成Seata时通过全局事务ID实现跨数据源协调
3. 详细配置指南
3.1 基础环境准备
建议使用以下环境配置:
bash复制# JDK版本
java -version # 1.8.0_202+
# Maven依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
# 推荐配合使用
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
3.2 多数据源配置详解
完整配置示例:
yaml复制spring:
datasource:
dynamic:
primary: master # 默认数据源
strict: true # 是否严格匹配模式
datasource:
master:
url: jdbc:mysql://localhost:3306/main_db?useSSL=false
username: admin
password: ENC(密文) # 支持Jasypt加密
driver-class-name: com.mysql.cj.jdbc.Driver
hikari: # 连接池专属配置
maximum-pool-size: 20
connection-timeout: 30000
slave_1:
url: jdbc:mysql://replica1:3306/read_db
username: reader
password: ENC(密文)
oracle_db:
url: jdbc:oracle:thin:@//oracle_host:1521/ORCL
username: system
password: oracle123
driver-class-name: oracle.jdbc.OracleDriver
配置说明表格:
| 配置项 | 说明 | 默认值 | 注意事项 |
|---|---|---|---|
| primary | 默认数据源 | master | 必须存在对应的数据源配置 |
| strict | 严格模式 | false | true时未匹配到数据源会抛异常 |
| datasource. | 数据源定义 | - | 支持任意多个数据源 |
| hikari/druid | 连接池配置 | - | 不配置时使用默认值 |
4. 实战应用示例
4.1 基础使用模式
4.1.1 类级别注解
java复制@DS("slave_1")
@Repository
public class UserReadMapper extends BaseMapper<User> {
// 所有方法默认使用slave_1数据源
}
4.1.2 方法级别注解
java复制@DS("master")
@Repository
public class OrderMapper extends BaseMapper<Order> {
@DS("slave_1") // 优先使用方法级别注解
public Order selectByOrderNo(String orderNo) {
// 该方法使用slave_1数据源
}
}
4.2 高级应用场景
4.2.1 读写分离实现
java复制@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@DS("master") // 写操作走主库
public void createUser(User user) {
userMapper.insert(user);
}
@DS("slave") // 读操作走从库
public User getUser(Long id) {
return userMapper.selectById(id);
}
}
4.2.2 多租户数据隔离
java复制public class TenantContext {
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
public static void setTenant(String tenant) {
CURRENT_TENANT.set(tenant);
}
public static String getTenant() {
return CURRENT_TENANT.get();
}
}
@Aspect
@Component
public class TenantDataSourceAspect {
@Before("@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping)")
public void before() {
String tenant = TenantContext.getTenant();
if (tenant != null) {
DynamicDataSourceContextHolder.push(tenant + "_ds");
}
}
}
5. 性能优化建议
5.1 连接池配置
推荐配置(基于HikariCP):
yaml复制spring:
datasource:
dynamic:
datasource:
master:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 600000
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
5.2 监控指标暴露
添加以下配置暴露监控端点:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,datasource
endpoint:
health:
show-details: always
通过/metrics端点可以监控:
- hikaricp.connections.active
- hikaricp.connections.idle
- hikaricp.connections.pending
6. 常见问题排查
6.1 典型问题汇总
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 数据源切换不生效 | 1. 注解位置错误 2. AOP顺序问题 |
1. 检查注解是否被继承 2. 添加@Order(0) |
| 事务内切换失败 | 事务已绑定连接 | 使用PROPAGATION_REQUIRES_NEW |
| 连接泄漏 | 未正确关闭连接 | 检查finally块是否调用clear() |
| 性能下降 | 连接池配置不当 | 调整max-pool-size等参数 |
6.2 事务处理最佳实践
java复制@Service
public class OrderService {
@Transactional // 主事务
public void placeOrder(Order order) {
// 主库操作
orderMapper.insert(order);
// 需要新事务和新数据源的操作
updateInventory(order);
}
@DS("inventory_db")
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateInventory(Order order) {
// 独立事务操作
}
}
7. 扩展进阶功能
7.1 动态增删数据源
运行时动态添加数据源:
java复制@Autowired
private DynamicDataSourceProvider provider;
public void addDataSource(String name, DataSourceProperty property) {
DynamicDataSourceCreator creator = new HikariDataSourceCreator();
DataSource dataSource = creator.createDataSource(property);
DynamicDataSourceContextHolder.addDataSource(name, dataSource);
}
7.2 多数据源事务方案
使用Seata实现分布式事务:
- 添加依赖:
xml复制<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
- 配置Seata:
yaml复制seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_test_tx_group
service:
vgroup-mapping:
my_test_tx_group: default
- 使用全局事务:
java复制@GlobalTransactional
public void crossDatabaseOperation() {
// 跨数据源操作
}
8. 最佳实践总结
在实际项目中使用多数据源时,我有以下几点经验分享:
-
命名规范:数据源名称采用有意义的命名,如"order_master"、"user_slave"等,避免使用简单的ds1、ds2
-
监控告警:对每个数据源的连接池状态设置监控,当active连接数超过80%时触发告警
-
故障演练:定期模拟从库故障,测试系统自动降级到主库的能力
-
性能测试:多数据源场景下要特别关注连接池配置,避免因连接数不足导致系统瓶颈
-
文档维护:建立数据源矩阵文档,记录每个数据源的用途、配置参数、负责人等信息
一个典型的项目结构建议:
code复制src/main/java
├── config
│ └── datasource # 数据源相关配置
├── constants
│ └── DataSourceConstants.java # 数据源名称常量
├── aspect
│ └── DataSourceAspect.java # 数据源切面
└── service
├── impl
│ ├── OrderServiceImpl.java # 订单服务
│ └── UserServiceImpl.java # 用户服务
└── query
├── OrderQueryServiceImpl.java # 订单查询服务
└── UserQueryServiceImpl.java # 用户查询服务