mybatis多数据源切换不能仅靠@ds注解生效,因其需配合aop切面与abstractroutingdatasource的determinecurrentlookupkey()实现;常见问题包括切面未生效、线程上下文未正确设置、事务内切换失效及mybatis-plus冲突等。

MyBatis 多数据源切换为什么不能只靠 @DS 注解自动生效
因为 MyBatis 本身不识别 @DS 这类自定义注解,它只是个标记;真正起作用的是你写的 AOP 切面 + AbstractRoutingDataSource 的 determineCurrentLookupKey() 方法。如果切面没拦到 DAO 方法、或者线程上下文没设对、或者数据源没注册进 Spring 容器,@DS 就纯属摆设。
常见错误现象:No qualifying bean of type 'javax.sql.DataSource' available、切换后仍走默认库、事务内切换失效。
- 确保 AOP 切点覆盖的是
Mapper接口的代理方法(不是 XML 中的 SQL),推荐用@Within(com.example.mapper..*)或明确切Mapper包 -
@DS必须加在接口方法上(不是实现类),且优先级高于类级注解;若方法和类都标了,方法级生效 - Spring 事务默认在代理对象调用时才生效,直接
this.xxxMapper.select()会绕过 AOP,导致注解失效
AbstractRoutingDataSource 的 determineCurrentLookupKey() 怎么写才不出错
这个方法返回的 key 必须和 setTargetDataSources() 里注册的 key 完全一致(包括大小写、空格),否则会 fallback 到 defaultTargetDataSource,而你可能根本没配它,结果抛 IllegalStateException: DataSource router no longer supports null keys。
典型使用场景:读写分离、按业务分库(如订单库/用户库)、测试环境动态路由。
- 用
ThreadLocal<string></string>存当前 key,务必在每次请求结束(如 Filter 或 @After)里remove(),否则线程复用时污染后续请求 - 不要在
determineCurrentLookupKey()里做耗时操作(如查 DB、远程调用),它会在每次getConnection()时被调用 - 如果用了 HikariCP,注意它的连接池是 per-dataSource 的,切换 key 不等于切换物理连接,只是路由到不同池
事务内 @DS 切换为什么经常失效
Spring 的 @Transactional 在方法入口就绑定好数据源连接,并在整个事务生命周期内复用它。你在事务方法里再调用另一个带 @DS 的 Mapper,AOP 是触发了,但 SqlSession 已经绑定了初始数据源,不会重新获取连接 —— 所以切换无效,还可能报 Cannot change transaction isolation level after connection has been obtained。
这不是 bug,是事务传播机制决定的。
- 跨数据源操作必须拆成独立事务(
@Transactional(propagation = Propagation.REQUIRES_NEW)),但要注意事务隔离和异常回滚边界 - 避免在同一个
@Transactional方法里混用多个@DS标记的 Mapper 调用 - 如果真要单事务多源,得用 JTA(如 Atomikos),但 MyBatis 原生不支持,需额外适配,复杂度陡增
Spring Boot 2.7+ 配多数据源时 mybatis-plus 的 @DS 和自定义 AOP 冲突怎么办
MyBatis-Plus 自带的 @DS 和你自己写的 AOP 切面会同时生效,导致数据源被切换两次:第一次是你 AOP 设的 key,第二次是 MP 的拦截器又覆盖成它自己的逻辑,最终行为不可控。
最稳妥的做法是停用 MP 的动态数据源功能,只用它生成 SQL,把路由完全交给自己控制。
- 在配置类里显式关闭 MP 的数据源路由:
mybatis-plus.global-config.db-config.id-type=auto并**不启用**DynamicDataSourceAutoConfiguration - 删掉
mybatis-plus-extension依赖中自带的DynamicDataSourceAnnotationAdvisor相关 Bean - 检查
DataSourceBean 名称是否冲突 —— MP 默认注册叫dataSource,你的主从数据源别也叫这名字,否则会被覆盖
动态数据源本质是线程上下文 + 路由策略的组合,不是框架特性而是设计选择;一旦涉及事务、异步、批量操作,那个 ThreadLocal 就特别容易漏清理或传不下去。这点比写法本身更值得盯紧。










