@DS 注解位置与 MyBatis-Plus 调用姿势
关联 Issue:#259、#104、#344、#355、#25、#59、#207、#337、#709、#73、#633、#589
一、@DS 应该加在哪?
推荐:Service 实现类的方法上。 维护者在 #259 给出的三条理由:
- 底层兼容性——3.2.0 起才解决了大部分「注解加在 Mapper 上」可能发生的异常;
- Service 才是业务属性的载体,由它负责决定选哪个库;
- 只有确定某个 Mapper 只属于某个数据源时,才可以加在 Mapper 上。
优先级规则:
代码中主动切换(ContextHolder.push) > 方法上的注解 > 类上的注解
接口和父类上声明的 @DS 可以被继承,完整规则见@DS 语义与优先级。
二、MyBatis-Plus 的调用姿势(历史 35+ issue 的重灾区)
@DS 依赖 Spring AOP 拦截「代理上的、带注解的方法」。而 MP 的许多便捷 API 恰好绕过了这一层。
错误姿势 1:Controller 直接使用 IService 默认方法
// Controller —— 不会切换!
@GetMapping("/list")
public List<User> list() {
return userService.list(); // IService 的 default 方法
}
list()、getById()、lambdaQuery()、saveBatch() 等在 IService/ServiceImpl 中是以接口 default 方法 / AbstractChainWrapper 形式提供的,实际执行路径不在「你注解的实现类方法」上(#25、#59、#207、#337、#355)。
正确姿势:自定义业务方法 + 注解,方法内自由使用 MP API
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 注解加在你自己声明的 public 方法上,方法内部无论用什么 MP API 都生效
@Override
@DS("slave")
public List<User> selectSlaveUsers() {
return baseMapper.selectList(null); // BaseMapper 直调 —— OK
}
@Override
@DS("slave")
public List<User> selectLambdaSlaveUsers() {
return this.lambdaQuery().list(); // lambda 链式 —— OK(因为执行入口在这个被代理的方法里)
}
}
关键词是执行入口:只要走进被
@DS注解的代理方法之后再调用 MP 的任何 API,都处在切换后的上下文里。 官方示例见 mybatisplus3-sample 的UserServiceImpl。
错误姿势 2:Controller 调 lambdaQuery() 却期望切库
// Controller —— 不切换!
public List<User> list() {
return userService.lambdaQuery().eq(User::getAge, 18).list();
}
链式的 list() 由 wrapper 执行,未经过你注解的方法(#709)。
saveBatch / updateBatchById
MP 3.4+ 中 ServiceImpl 的默认批量实现存在类似的绕过路径,官方 sample 选择了自定义实现(UserServiceImpl#saveBatch)。
建议:在自定义方法上标 @DS,内部调用 baseMapper.insert/update 逐条执行,updateBatchById 同理。
ActiveRecord 不支持
MP 的 ActiveRecord 模式(Model 的 insert()/selectById() 等静态/实例方法)不经过 Spring Bean 代理,@DS 无法拦截(#589)。请改用传统 Service + Mapper 方式。
databaseId 不随切换变化
一个已知限制:@DS 切换数据源时,MyBatis 的 databaseId(多数据库厂商 XML 方言选择器)不会跟着切换,同一个 Mapper 在不同厂商的库之间切会执行同一份方言(#563、#718)。不同厂商多库建议使用不同的 Mapper/Service。
三、加在 Mapper 上的注意点
- 3.2.0+ 大部分场景可用;低版本会出现
Invalid bound statement、注解不识别等异常(#183、#208); - 若 Mapper 上的
@DS与外层 Service 的@DS冲突,以**就近(Mapper 方法)**优先; - 生产建议仍以 Service 层为主:Mapper 层切库不利于复用与事务规划。
相关文档
- @DS 不生效排查总表
- @DS 语义与优先级 —— 组名、匹配规则与完整优先级
- 集成 MyBatis-Plus
- 手动切换数据源 —— 注解无法覆盖时的替代方案