MyBatis-Plus多数据源配置与动态切换实战
1. 项目概述
作为一名常年与数据库打交道的Java开发者,我深刻理解多数据源配置在实际项目中的重要性。当系统需要同时连接多个数据库(比如主从分离、分库分表、多租户等场景)时,传统的单数据源方案就显得捉襟见肘。MyBatis-Plus作为MyBatis的增强工具,配合dynamic-datasource-spring-boot-starter组件,可以优雅地解决这个问题。
这个方案的核心价值在于:
- 零侵入性:无需修改原有MyBatis-Plus代码
- 动态切换:通过注解即可实现运行时数据源切换
- 配置简单:Spring Boot风格的配置方式
- 功能完善:支持事务、读写分离等高级特性
我最近在电商项目中就遇到了这样的需求:需要同时操作业务数据库和日志数据库。经过多种方案对比,最终选择了MyBatis-Plus多数据源方案,实测下来非常稳定。下面就把我的配置过程和踩坑经验分享给大家。
2. 环境准备与依赖配置
2.1 版本匹配要点
在开始之前,版本兼容性是首要考虑的问题。根据我的经验,版本不匹配会导致90%的配置问题。以下是经过验证的稳定版本组合:
<properties> <spring-boot.version>2.7.3</spring-boot.version> <mybatis-plus.version>3.5.1</mybatis-plus.version> <dynamic-datasource.version>3.6.1</dynamic-datasource.version> </properties>注意:MyBatis-Plus 3.5.x系列与Spring Boot 2.7.x兼容性最好。如果使用Spring Boot 3.x,需要对应升级MyBatis-Plus到最新版。
2.2 核心依赖引入
在pom.xml中添加以下依赖:
<dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- MyBatis-Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!-- 多数据源核心 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>${dynamic-datasource.version}</version> </dependency> <!-- 数据库驱动(以MySQL为例) --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> </dependencies>3. 多数据源配置详解
3.1 基础配置
在application.yml中配置多数据源:
spring: datasource: dynamic: primary: master # 设置默认数据源 strict: false # 是否严格匹配数据源,默认false datasource: master: url: jdbc:mysql://localhost:3306/master_db?useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver slave: url: jdbc:mysql://localhost:3306/slave_db?useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver关键参数说明:
primary: 指定默认数据源strict: 设为true时,未匹配到数据源会抛出异常- 每个数据源的配置与传统单数据源配置相同
3.2 高级配置选项
实际项目中我们通常需要更精细的控制:
spring: datasource: dynamic: hikari: connection-timeout: 30000 max-lifetime: 1800000 max-pool-size: 15 min-idle: 5 datasource: master: # ...其他配置 hikari: pool-name: masterHikariCP slave: # ...其他配置 hikari: pool-name: slaveHikariCP这样可以为不同数据源单独配置连接池参数,优化性能。
4. 数据源切换实战
4.1 注解式切换
@DS注解是切换数据源的核心:
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; // 使用master数据源 @DS("master") public void addUser(User user) { userMapper.insert(user); } // 使用slave数据源 @DS("slave") public User getUserById(Long id) { return userMapper.selectById(id); } }4.2 方法调用链中的注意事项
在方法调用链中,数据源切换遵循以下规则:
- 外层方法没有
@DS注解时,内层方法注解生效 - 外层方法有
@DS注解时,内层注解不生效 - 事务方法中切换数据源需要特殊处理(后面会讲)
@Service public class OrderServiceImpl implements OrderService { @DS("master") public void createOrder(Order order) { // 这里使用master数据源 orderMapper.insert(order); // 即使方法有@DS("slave"),实际仍使用master updateStatistics(order); } @DS("slave") public void updateStatistics(Order order) { // 由于被createOrder调用,这里的slave不生效 } }5. 事务处理技巧
5.1 多数据源事务的坑
默认情况下,@Transactional和@DS注解一起使用会导致问题:
@DS("master") @Transactional public void transactionalMethod() { // 这里的事务可能不会按预期工作 }这是因为:
- Spring事务基于AOP实现
- 事务切面在数据源切换切面之前执行
- 导致事务内使用的数据源不正确
5.2 解决方案
方案一:使用DSTransactional注解(推荐)
@DS("master") @DSTransactional public void safeTransactionalMethod() { // 现在事务和数据源都能正确工作 }方案二:调整切面顺序
@Configuration public class DataSourceConfig { @Bean public DynamicDataSourceAnnotationAdvisor dynamicDataSourceAnnotationAdvisor() { DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(); advisor.setOrder(Ordered.HIGHEST_PRECEDENCE); return advisor; } }6. 读写分离实战
6.1 配置读写分离
spring: datasource: dynamic: primary: master datasource: master: url: jdbc:mysql://master-host:3306/db username: root password: 123456 slave_1: url: jdbc:mysql://slave1-host:3306/db username: root password: 123456 slave_2: url: jdbc:mysql://slave2-host:3306/db username: root password: 123456 strategy: # 负载均衡策略 slave: round_robin # 轮询6.2 使用策略
@Service public class UserServiceImpl implements UserService { // 写操作使用master @DS("master") public void addUser(User user) { // insert操作 } // 读操作自动负载均衡到slave @DS("slave") public User getUser(Long id) { // select操作 } }7. 常见问题排查
7.1 数据源未切换
现象:添加了@DS注解但数据源没有切换
排查步骤:
- 检查注解是否写在接口上(应该写在实现类)
- 检查方法是否是public(非public方法注解不生效)
- 检查是否被同类方法调用(自调用注解不生效)
7.2 事务不生效
现象:事务回滚失败
解决方案:
- 使用
@DSTransactional替代@Transactional - 或者确保事务方法的数据源与
@DS一致
7.3 性能问题
现象:系统变慢
优化建议:
- 为不同数据源配置独立的连接池参数
- 监控连接泄漏
- 合理设置超时时间
8. 最佳实践总结
经过多个项目的实践,我总结了以下经验:
- 命名规范:数据源名称要有意义,如
order_master、log_slave等 - 监控配置:集成Druid监控每个数据源的状态
- 压测验证:上线前模拟多数据源并发场景
- 降级方案:主库不可用时自动降级到从库
- 文档记录:团队内部维护数据源使用规范
最后分享一个实用技巧:在开发环境可以使用H2内存数据库作为备选数据源,避免因数据库服务不可用阻塞开发:
spring: datasource: dynamic: datasource: dev_mem: url: jdbc:h2:mem:testdb username: sa password: driver-class-name: org.h2.Driver