SpringBoot 自定义注解 实现多数据源

SpringBoot自定义注解实现多数据源前置学习

需要了解 注解、Aop、SpringBoot整合Mybatis的使用 。
数据准备基础项目代码:https://gitee.com/J_look/spring-boot-all-demo
数据库SQL 项目中有提供,修改基本信息即可
行动起来添加依赖
利用 AOP 可以实现对某些代码的解耦,不需要硬编码编写 。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>开启AOP支持
也可以像图中一样,也开启事务管理器,下文会演示事务失效的问题 。

SpringBoot 自定义注解 实现多数据源

文章插图
定义枚举
【SpringBoot 自定义注解 实现多数据源】这里定义的枚举,代表我们不同的数据库 。
public enum DataSourceType {MYSQL_DATASOURCE1,MYSQL_DATASOURCE2,}定义数据源管理器
由于本人实力原因,解答不了大家这里的疑惑 。大致功能 通过修改本地线程的值,来实现数据源的切换
@Component@Primarypublic class DataSourceManagement extends AbstractRoutingDataSource {public static ThreadLocal<String> flag = new ThreadLocal<>();/*** 注入数据源*/@Resourceprivate DataSource mysqlDataSource1;/*** 注入数据源*/@Resourceprivate DataSource mysqlDataSource2;public DataSourceManagement() {flag.set(DataSourceType.MYSQL_DATASOURCE1.name());}@Overrideprotected Object determineCurrentLookupKey() {return flag.get();}@Overridepublic void afterPropertiesSet() {Map<Object, Object> targetDataSource = new ConcurrentHashMap<>();targetDataSource.put(DataSourceType.MYSQL_DATASOURCE1.name(), mysqlDataSource1);targetDataSource.put(DataSourceType.MYSQL_DATASOURCE2.name(), mysqlDataSource2);// 设置数据源来源super.setTargetDataSources(targetDataSource);// 设置默认数据源super.setDefaultTargetDataSource(mysqlDataSource1);super.afterPropertiesSet();}}自定义注解
@target:表示自定义注解能用在哪里
@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface TargetDataSource {DataSourceType value() default DataSourceType.MYSQL_DATASOURCE1;}定义切面类
可以看到,我们的切面类采用的是环绕通知,博主在写这篇文章之前,做过大量测试,也参考过比较多的文章,总结以下几点:
  • 采用前置通知,虽然能实现数据源的切换,但是会导致事务失效 。(推荐视频)
  • 采用环绕通知,事务不会失效,但是切换数据源却实现不了 。(由Bean的加载顺序导致的,下文中的@Order就可以解决,@Transactional默认级别是最后加载 。可以查看日志信息知晓 。)
@Component@Aspect@Slf4j@Order(Ordered.LOWEST_PRECEDENCE-1) // Bean加载顺序public class TargetDataSourceAspect {@Around("@within(TargetDataSource) || @annotation(TargetDataSource)")public Object beforeNoticeUpdateDataSource(ProceedingJoinPoint joinPoint) {TargetDataSource annotation = null;Class<? extends Object> target = joinPoint.getTarget().getClass();if (target.isAnnotationPresent(TargetDataSource.class)) {// 判断类上是否标注着注解annotation = target.getAnnotation(TargetDataSource.class);log.info("类上标注了注解");} else {Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();if (method.isAnnotationPresent(TargetDataSource.class)) {// 判断方法上是否标注着注解,如果类和方法上都没有标注,则报错annotation = method.getAnnotation(TargetDataSource.class);log.info("方法上标注了注解");} else {throw new RuntimeException("@TargetDataSource注解只能用于类或者方法上, 错误出现在:[" +target.toString() + " " + method.toString() + "];");}}// 切换数据源DataSourceManagement.flag.set(annotation.value().name());Object result = null;try {// 执行目标代码result = joinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}return result;}}

经验总结扩展阅读