通过 AOP 注解实现自定义数据源切换
背景
近期在重构项目中,由于部分查询的数据库配置是根据不同的用户进行个性化配置的,所以在查询过程中,存在 MyBatis 多源数据库切换问题。
原项目实现方式为,在 MyBatis 查询前,切换到个性化配置,查询完毕后,再切换回默认数据库配置。
现在希望通过注解标注形式,进行重构代码。
原先实现方式
public Remark getRemarkById(Integer id, UserBase userBase){
SpecialDataSourceUtils.setDBType(DataSourceNameConstant.USER_DB + userBase.getOrgId()); // 切换到个性化配置
Remark remark = remarkMapper.selectById(id);
DataSourceContextHolder.setDBType(DataSourceNameConstant.DEFAULT_DB); // 切换回默认数据库配置
return remark;
}
AOP 方法注解
注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ChangeMySqlMethod {
boolean isChange() default true;
}
Aspect 切面类
@Slf4j
@Aspect
@Component
@Order(1)
public class ChangeMySqlMethodAspect {
@Autowired
DataBasesConfig dataBasesConfig;
@Pointcut("@annotation(com.netease.mdas.databases.annotations.ChangeMySqlMethod)")
public void changeMySQL() {}
@Before("changeMySQL() && @annotation(changeMySqlMethod)")
public void doBefore(JoinPoint joinPoint, ChangeMySqlMethod changeMySqlMethod) {
log.info("====doBefore方法进入了====");
if (changeMySqlMethod.isChange()){
SpecialDataSourceUtils.setDBType(DataSourceNameConstant.USER_DB + ActiveInfo.orgId());
MyBatisPlusConfig.setSuffix(ActiveInfo.orgId());
} else {
DataSourceContextHolder.setDBType(DataSourceNameConstant.DEFAULT_DB);
}
}
@After("changeMySQL()")
public void doAfter(JoinPoint joinPoint) {
log.info("====doAfter方法进入了====");
DataSourceContextHolder.setDBType(DataSourceNameConstant.DEFAULT_DB);
}
}
重构后实现方式
@ChangeMySqlMethod // 方法注解
public Remark getRemarkById(Integer id){
return remarkMapper.selectById(id);
}
进一步思考
AOP 方法注解的实现方式,与原先实现方式相比,已经简洁了很多。
但是,如果 Service 的方法特别多,每个方法上面都加上注解的方式也会显得很繁琐。
同时,如果方法中有多个数据源同时查询的情况,还需要重新根据不同的数据源拆分方法,从而保证每个方法中只涉及一个数据源的查询。这样的重构方式并不友好。
那么,有没有办法在 RemarkMapper 类上面直接添加注解,来实现数据源动态切换呢?
由于 Mapper 类实现类是 MyBatis 动态代理生成,无法在实现类上直接添加 AOP 注解,而添加到接口上又无效,所以需要寻找替代方案。
AOP 类注解
不采用 AspectJ 表达式方式定义切点和切面,使用 AnnotationMatchingPointcut 和 DefaultPointcutAdvisor 来定义。
注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ChangeMySqlType {
}
Aspect 切面类
@Slf4j
@Aspect
@Component
@Order(2)
public class ChangeMySqlTypeAspect {
@Autowired
DataBasesConfig dataBasesConfig;
@Bean
public Advisor dataSourceAdvisor(){
Pointcut pointcut = new AnnotationMatchingPointcut(ChangeMySqlType.class, true);
Advice advice = new MethodAroundAdvice(dataBasesConfig);
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
Advice 业务增强类
@Slf4j
@Component
public class MethodAroundAdvice implements MethodBeforeAdvice, AfterReturningAdvice {
private DataBasesConfig dataBasesConfig;
public MethodAroundAdvice(DataBasesConfig dataBasesConfig){
this.dataBasesConfig = dataBasesConfig;
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
log.info("before {} called", method.getName());
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
log.info("after {} called", method.getName());
}
}
重构后的实现方式
Mapper:
@ChangeMySqlType // 类注解
public interface RemarkMapper extends BaseMapper<Remark>{}
方法:
public Remark getRemarkById(Integer id){
return remarkMapper.selectById(id);
}
至此,可以在 Mapper 类上面直接添加注解,来实现数据源动态切换。
相关文章