MyBatis-Plus多数据源配置与动态路由实战
2026/8/4 11:55:10 网站建设 项目流程

1. 多数据源配置的核心价值与场景解析

在真实的企业级应用开发中,单数据源往往难以满足复杂的业务需求。我经历过一个电商项目,需要同时对接主业务库、财务库和物流库,传统的单数据源模式会导致代码中充斥大量硬编码的JDBC连接切换逻辑。MyBatis-Plus结合dynamic-datasource的多数据源方案,正是解决这类痛点的利器。

这种方案的核心优势在于:

  • 业务透明性:开发人员无需关心具体操作哪个数据库,通过注解即可切换
  • 动态路由能力:可根据业务参数在运行时动态选择数据源
  • 事务管理:支持跨数据源的分布式事务(需结合Seata等框架)
  • 性能优化:内置连接池管理,避免频繁创建销毁连接

典型应用场景包括:

  • 多租户SaaS系统(每个租户独立数据库)
  • 读写分离架构(主库写,从库读)
  • 分库分表前的过渡方案
  • 异构数据库整合(如MySQL+Oracle混合使用)

重要提示:多数据源配置会增加系统复杂度,在单库能满足需求时不要过度设计。我曾见过团队为了"技术先进性"强行上多数据源,反而导致维护成本飙升的案例。

2. 环境准备与依赖配置

2.1 版本匹配要点

当前主流组合方案:

  • Spring Boot 2.7.x + MyBatis-Plus 3.5.3.x
  • Spring Boot 3.x + MyBatis-Plus 3.5.4+

我推荐使用经过验证的稳定组合:

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.6.1</version> </dependency>

2.2 Druid连接池集成

虽然dynamic-datasource自带HikariCP,但国内项目更常用Druid。配置示例:

spring: datasource: druid: stat-view-servlet: enabled: true login-username: admin login-password: admin filters: stat,wall filter: stat: slow-sql-millis: 1000 log-slow-sql: true

2.3 多数据源基础配置

spring: datasource: dynamic: primary: master # 默认数据源 strict: false # 是否严格匹配数据源 datasource: master: url: jdbc:mysql://localhost:3306/master_db username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource slave1: url: jdbc:mysql://localhost:3306/slave_db1 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource

3. 核心实现与注解使用

3.1 @DS注解深度解析

@DS是动态数据源的核心注解,支持以下使用场景:

  1. 方法级注解(最常用):
@DS("slave1") public List<User> getUsers() { return userMapper.selectList(null); }
  1. 类级注解(该类所有方法默认使用该数据源):
@DS("master") @Repository public class UserDaoImpl implements UserDao { // 所有方法默认使用master数据源 }
  1. 注解继承规则:
  • 方法注解优先于类注解
  • 接口上的注解不生效(因为Spring AOP基于实现类)

3.2 动态数据源切换原理

核心流程:

  1. 通过AOP拦截被@DS注解的方法
  2. 从注解值或SpEL表达式中解析目标数据源名称
  3. 将数据源key设置到DynamicDataSourceContextHolder
  4. 执行SQL前通过AbstractRoutingDataSource确定实际数据源
  5. 执行完成后清理线程上下文

踩坑记录:我曾遇到@DS注解在同类方法调用时不生效的问题,这是因为Spring AOP的代理机制导致的。解决方案是注入自身代理或通过ApplicationContext获取bean。

4. 高级特性与实战技巧

4.1 分页插件集成

MyBatis-Plus的分页在多数据源环境下需要特殊处理:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

使用示例:

@DS("slave1") public Page<User> getUsersByPage(int pageNum, int pageSize) { Page<User> page = new Page<>(pageNum, pageSize); return userMapper.selectPage(page, null); }

4.2 多数据源事务管理

默认情况下,@Transactional和@DS注解组合使用时会有问题。解决方案:

  1. 方案一:使用分布式事务框架(如Seata)
  2. 方案二:避免跨数据源事务
  3. 方案三:自定义事务管理器
@DS("master") @Transactional(rollbackFor = Exception.class) public void updateMaster(User user) { userMapper.updateById(user); // 调用其他数据源方法需要特殊处理 ((UserService)AopContext.currentProxy()).updateSlave(user); } @DS("slave1") public void updateSlave(User user) { userMapper.updateById(user); }

4.3 动态数据源扩展

实现动态数据源路由:

public class DynamicDataSourceStrategy implements DynamicDataSourceStrategy { @Override public String determineDataSourceKey( DynamicDataSourceContextHolder contextHolder) { // 从请求参数或线程上下文获取路由key String tenantId = TenantContext.getCurrentTenant(); return "ds_" + tenantId; } }

配置:

spring: datasource: dynamic: strategy: com.example.config.DynamicDataSourceStrategy

5. 性能优化与问题排查

5.1 连接池配置建议

spring: datasource: druid: # 初始连接数 initial-size: 5 # 最小空闲连接数 min-idle: 5 # 最大活跃连接数 max-active: 20 # 获取连接等待超时时间 max-wait: 60000 # 配置间隔多久检测需要关闭的空闲连接 time-between-eviction-runs-millis: 60000 # 连接最小生存时间 min-evictable-idle-time-millis: 300000

5.2 常见问题排查指南

问题现象可能原因解决方案
数据源切换不生效1. 注解位置错误
2. 同类方法调用
1. 检查注解位置
2. 使用AopContext
事务回滚失效跨数据源事务使用分布式事务或重构代码
连接泄漏连接未正确关闭检查事务边界,配置Druid监控
性能下降连接池配置不当调整连接池参数,监控使用情况

5.3 监控与调优

  1. 启用Druid监控:
@Bean public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() { ServletRegistrationBean<StatViewServlet> reg = new ServletRegistrationBean<>(); reg.setServlet(new StatViewServlet()); reg.addUrlMappings("/druid/*"); return reg; }
  1. 关键监控指标:
  • 活跃连接数
  • 等待线程数
  • SQL执行时间
  • 慢SQL记录

6. 项目实战:若依框架改造案例

以若依4.8.3非分离版改造为例:

  1. 移除原有Mybatis依赖,添加Mybatis-Plus:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>
  1. 修改application.yml配置多数据源

  2. 重构原有Mapper接口:

// 原接口 public interface UserMapper { @Select("SELECT * FROM sys_user WHERE user_id = #{userId}") SysUser selectUserById(Long userId); } // 改为 public interface UserMapper extends BaseMapper<SysUser> { @DS("slave") // 添加数据源注解 @Select("SELECT * FROM sys_user WHERE user_id = #{userId}") SysUser selectUserById(Long userId); }
  1. 分页改造:
// 原代码 PageHelper.startPage(pageNum, pageSize); List<SysUser> list = userMapper.selectUserList(user); PageInfo<SysUser> pageInfo = new PageInfo<>(list); // 改为 Page<SysUser> page = new Page<>(pageNum, pageSize); userMapper.selectPage(page, Wrappers.<SysUser>lambdaQuery() .eq(SysUser::getStatus, "0"));

改造过程中的经验:

  • 先从小模块开始试点
  • 注意原有XML中特殊SQL的兼容处理
  • 分页参数传递方式的变化需要统一调整

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询