1. 多数据源配置的核心价值与应用场景
在真实的企业级应用开发中,单数据源往往难以满足复杂的业务需求。我经历过多个需要同时对接Oracle财务库和MySQL业务库的项目,也处理过读写分离场景下的数据一致性问题。MyBatis-Plus的多数据源方案之所以备受青睐,关键在于它解决了以下痛点:
- 混合业务系统整合:当需要同时访问遗留系统的SQL Server和新业务的PostgreSQL时
- 读写分离架构:主库写从库读的场景下,如何避免手动切换数据源
- 分库分表扩展:水平拆分的数据库实例需要统一管理
- 多租户SaaS系统:每个租户独立数据库实例的情况
特别注意:多数据源与分布式事务是两个不同维度的问题。本文方案适用于非跨库事务或能接受最终一致性的场景,如需强一致性需结合Seata等方案
2. 技术选型与方案对比
2.1 主流多数据源实现方式
在Spring生态中,实现多数据源主要有三种技术路线:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| AbstractRoutingDataSource | 轻量级,Spring原生支持 | 需自行处理事务管理 | 简单读写分离 |
| MyBatis-Plus多数据源 | 开箱即用,功能完善 | 学习曲线略高 | 企业级复杂场景 |
| JPA+Hibernate | ORM层统一 | 性能损耗较大 | 全JPA技术栈 |
2.2 MyBatis-Plus方案核心优势
- 动态数据源切换:通过@DS注解实现方法级别的数据源路由
- 连接池智能管理:集成HikariCP、Druid等主流连接池
- 事务边界控制:提供@DSTransactional注解处理跨库事务
- 敏感信息加密:支持数据库密码ENC()加密存储
3. 完整实现步骤详解
3.1 环境准备与依赖配置
<!-- pom.xml关键依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency>3.2 多数据源配置模板
# application.yml标准配置 spring: datasource: dynamic: primary: master # 设置默认数据源 strict: false # 是否严格匹配数据源 datasource: master: url: jdbc:mysql://127.0.0.1:3306/master?useSSL=false username: root password: ENC(密文) # 使用jasypt加密 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource slave_1: url: jdbc:mysql://127.0.0.1:3306/slave1?useSSL=false username: slave_user password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver3.3 核心功能实现
3.3.1 基础数据源切换
@Service public class OrderServiceImpl implements OrderService { @DS("master") // 默认数据源可不写 public void createOrder(Order order) { // 主库写操作 } @DS("slave_1") public Order getOrderById(Long id) { // 从库读操作 } }3.3.2 动态数据源进阶用法
// 基于参数动态切换 public class DataSourceSelector { public static String determineDataSource(String tenantId) { return "tenant_" + tenantId; } } @DS("#header.tenant") @GetMapping("/data") public List<Object> getData(@RequestHeader String tenant) { // 根据header自动路由 }4. 生产环境注意事项
4.1 连接池配置优化
master: initial-size: 5 max-active: 20 min-idle: 5 max-wait: 60000 validation-query: SELECT 1 FROM DUAL test-while-idle: true关键参数建议:
- 每个数据源的max-active总和不应超过数据库最大连接数
- 不同业务场景应设置不同的连接池隔离级别
4.2 事务管理要点
- 本地事务:在方法内切换数据源时,需添加@Transactional(propagation = Propagation.NOT_SUPPORTED)
- 跨库事务:使用@DSTransactional注解(基于XA协议)
@DSTransactional public void crossDatabaseOperation() { // 操作master数据源 masterMapper.update(...); // 操作slave数据源 slaveMapper.insert(...); }5. 性能调优实战技巧
5.1 监控指标采集
@Bean public ServletRegistrationBean<StatViewServlet> druidServlet() { ServletRegistrationBean<StatViewServlet> reg = new ServletRegistrationBean<>(); reg.setServlet(new StatViewServlet()); reg.addUrlMappings("/druid/*"); return reg; }5.2 常见问题排查
问题1:出现"Could not open JDBC Connection"异常
- 检查项:
- 数据库连接数是否耗尽
- 网络策略是否开通
- 密码加密情况下是否配置了jasypt解密器
问题2:@DS注解不生效
- 排查路径:
- 确保方法是被Spring代理的(非private/final方法)
- 检查是否在同一个类中自调用
- 确认依赖版本无冲突
6. 扩展应用场景
6.1 多租户实现方案
public class TenantDataSourceSelector { public static String currentTenant() { return TenantContext.getCurrentTenant(); } } @DS("#tenantDataSourceSelector.currentTenant()") public class TenantService { // 业务方法 }6.2 读写分离自动路由
@Aspect @Component public class ReadOnlyAspect { @Around("@annotation(readOnly)") public Object around(ProceedingJoinPoint pjp, ReadOnly readOnly) throws Throwable { DynamicDataSourceContextHolder.push("slave"); try { return pjp.proceed(); } finally { DynamicDataSourceContextHolder.poll(); } } }在实际项目落地时,建议先在小规模非核心业务验证稳定性。我曾在一个电商项目中,通过这套方案将订单查询性能提升了3倍,同时保证了写操作的可靠性。关键是要做好连接池监控和故障转移方案,当某个数据源不可用时能自动降级。