1. 项目背景与核心价值
在真实的企业级应用开发中,多数据源支持几乎是刚需场景。我经历过太多项目因为早期没考虑多数据源导致后期重构的痛苦——从简单的读写分离、分库分表,到多租户架构下的数据隔离,甚至是跨业务系统的数据聚合查询。SpringBoot虽然简化了单数据源配置,但官方并未提供现成的多数据源解决方案。
MyBatis-Plus作为MyBatis的增强工具包,其多数据源模块(dynamic-datasource)真正实现了"配置即用"的体验。最近在金融项目中用它快速实现了交易库+日志库的双源切换,实测单服务支持5个以上数据源仍能保持性能稳定。下面分享这套方案的完整落地过程。
2. 环境准备与基础配置
2.1 依赖引入关键点
首先在pom.xml中引入核心依赖(以SpringBoot 2.7.x为例):
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>
注意:切勿同时引入druid-spring-boot-starter,这会导致连接池冲突。dynamic-datasource默认使用HikariCP,如需改用Druid需特殊配置(后文会说明)
2.2 配置文件详解
application.yml的标准配置模板:
yaml复制spring:
datasource:
dynamic:
primary: master # 默认数据源
strict: false # 是否严格匹配数据源
datasource:
master:
url: jdbc:mysql://localhost:3306/main_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave1:
url: jdbc:mysql://192.168.1.100:3306/read_db1
username: read_user
password: read123
driver-class-name: com.mysql.cj.jdbc.Driver
关键参数说明:
primary:指定默认数据源,当未标注@DS注解时使用strict:开启后访问未配置数据源会抛异常(生产环境建议true)- 每个数据源的配置项与SpringBoot原生配置完全一致
3. 核心功能实现
3.1 注解驱动切换方案
MyBatis-Plus通过@DS注解实现声明式数据源切换:
java复制@Service
public class OrderServiceImpl implements OrderService {
@DS("master") // 默认写操作走主库
public void createOrder(Order order) {
orderMapper.insert(order);
}
@DS("slave1") // 查询走从库
public Order getOrderById(Long id) {
return orderMapper.selectById(id);
}
}
注解可作用于类或方法:
- 类级别:该类所有方法默认使用指定数据源
- 方法级别:优先级高于类级别注解
- 无注解:使用primary指定的默认数据源
3.2 动态路由高级用法
对于需要运行时动态判断的场景,可以编程方式切换:
java复制public List<Order> getOrdersByShard(String region) {
// 根据业务规则计算数据源key
String dsKey = "slave_" + region.hashCode() % 2;
return DynamicDataSourceContextHolder.push(dsKey, () -> {
return orderMapper.selectList(null);
});
}
踩坑提醒:务必在finally块中执行DynamicDataSourceContextHolder.poll()清理线程变量,否则会导致后续操作仍使用当前数据源
4. 生产级优化策略
4.1 连接池调优建议
默认HikariCP配置可能不满足高并发需求,建议调整:
yaml复制spring:
datasource:
dynamic:
datasource:
master:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
如果必须使用Druid,需要额外配置:
java复制@Bean
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
public DataSource dataSource() {
return new DruidDataSource();
}
4.2 事务管理特别处理
多数据源环境下事务需要特殊处理:
java复制@Transactional
@DS("master")
public void crossDsOperation() {
// 主库操作
orderMapper.insert(order);
// 切换数据源(需要开启allowPublicMethodsOnly=false)
DynamicDataSourceContextHolder.push("slave1");
try {
logMapper.insert(log);
} finally {
DynamicDataSourceContextHolder.poll();
}
}
重要限制:默认只支持单数据源事务,跨库事务需要引入Seata等分布式事务框架
5. 性能监控与问题排查
5.1 监控指标接入
通过SpringBoot Actuator暴露数据源健康状态:
yaml复制management:
endpoints:
web:
exposure:
include: health,metrics
health:
db:
enabled: true
访问/actuator/health可查看各数据源状态:
json复制{
"db": {
"status": "UP",
"details": {
"master": { "status": "UP" },
"slave1": { "status": "UP" }
}
}
}
5.2 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 切换数据源不生效 | 1. 注解被事务注解覆盖 2. 方法访问权限非public |
1. 调整注解顺序 2. 检查方法修饰符 |
| 连接泄漏 | 未正确关闭动态上下文 | 使用try-finally确保清理 |
| 性能下降 | 连接池配置不合理 | 根据QPS调整maxPoolSize |
6. 扩展应用场景
6.1 多租户架构实现
结合TenantLineInnerInterceptor实现租户隔离:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(
new TenantLineHandler() {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
return new StringValue(currentTenant.get());
}
}
));
return interceptor;
}
6.2 读写分离最佳实践
通过AOP自动路由:
java复制@Around("@annotation(readOnly)")
public Object routeToSlave(ProceedingJoinPoint pjp, ReadOnly readOnly) {
DynamicDataSourceContextHolder.push("slave");
try {
return pjp.proceed();
} finally {
DynamicDataSourceContextHolder.poll();
}
}
在需要读操作的方法上添加自定义注解:
java复制@ReadOnly
public List<Order> queryOrders() {
return orderMapper.selectList(null);
}
这套方案已经在日订单量百万级的电商系统中验证过稳定性,关键是要做好连接池参数调优和异常处理。对于更复杂的场景,可以考虑结合ShardingSphere实现分库分表。