摘要: 本篇文章的场景是做调度中心和监控中心时的需求,后端使用 TDDL 实现分表分库,需求: 实现关键业务的查询监控, 当用 Mybatis 查询数据时需要从主库切换到备库或者直接连到备库上查询, 从而减小主库的压力,在本篇文章中主要记录在 Spring Boot 中通过自定义注解结合 AOP 实现直接连接备库查询。
一. 通过 AOP 自定义注解实现主库到备库的切换
1.1 自定义注解
自定义注解如下代码所示
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SwitchDataBase {boolean switch2Backup() default false;
}
1.2 实现方法拦截器对自定义注解处理
import java.lang.reflect.Method;
import java.util.Arrays;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 处理走备库逻辑的注解
*/
@Component
public class SwitchDataBaseInterceptor implements MethodInterceptor {private final Logger log = LoggerFactory.getLogger(SwitchDataBaseInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();
SwitchDataBase annotation = getAnnotation(method);
if (annotation == null) {return invocation.proceed();
}
Object val = null;
if(!ThreadLocalMap.containsKey(GroupDataSourceRouteHelper.DATASOURCE_INDEX)) {if (annotation.switch2Backup()) {log.info("query back up DB, method:" + method.getName());
GroupDataSourceRouteHelper.executeByGroupDataSourceIndex(1, true);
} else {log.info("query primary DB, method:" + method.getName());
GroupDataSourceRouteHelper.executeByGroupDataSourceIndex(0, true);
}
}
try {val = invocation.proceed();
} catch (Exception e) {log.info(method.getDeclaringClass().getName() + "." +
invocation.getMethod().getName() + "方法调用失败,arguments:" +
Arrays.toString(invocation.getArguments()));
throw new RuntimeException(e);
} finally {GroupDataSourceRouteHelper.removeGroupDataSourceIndex();
}
return val;
}
/**
* 找方法上面声明的注解
*/
private SwitchDataBase getAnnotation(Method method) {if (method.isAnnotationPresent(SwitchDataBase.class)) {return method.getAnnotation(SwitchDataBase.class);
}
return null;
}
}
1.3 配置 OverallQueryConfiguration
在 Spring Boot 中装配 AOP Bean,实现扫描特定目录下的注解,实现切面变成形成通知处理。示例代码如下
import com.wdk.wms.annotation.SwitchDataBaseInterceptor;
import org.springframework.aop.Advisor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwitchDataBaseConfiguration {@Bean(name = "overallQueryInterceptor")
public SwitchDataBaseInterceptor overallQueryInterceptor() {return new SwitchDataBaseInterceptor();
}
// 添加 aop 的 pointcut
@Bean(name = "jdkRegexpMethodPointcut")
public JdkRegexpMethodPointcut jdkRegexpMethodPointcut() {JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut();
jdkRegexpMethodPointcut.setPatterns("com.wdk.wms.mapper.*");
return jdkRegexpMethodPointcut;
}
// 设置默认的 aop 配置对应的是原来的 <aop:advisor>
@Bean
public Advisor druidAdvisor() {DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
defaultPointcutAdvisor.setPointcut(jdkRegexpMethodPointcut());
defaultPointcutAdvisor.setAdvice(overallQueryInterceptor());
return defaultPointcutAdvisor;
}
}
1.4 如何使用注解从主库到备库的切换
@SwitchDataBase(switch2Backup = true)
List<ConsumerNoticeMsg> listByTemplateOver3(@Param("templates") List<Integer> templates);