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支持也可以像图中一样 , 也开启事务管理器,下文会演示事务失效的问题 。
文章插图
定义枚举
这里定义的枚举,代表我们不同的数据库 。
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:表示自定义注解能用在哪里【SpringBoot 自定义注解 实现多数据源】
@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;}}
Service文章插图
启动测试类:
import look.word.datasource.service.BookService;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;/** * @author : look-word * 2022-10-10 23:11 **/@SpringBootTestpublic class TestBookMapper {@Resourceprivate BookService bookService;@Testvoid updatePrice() {System.out.println(bookService.updatePrice(1, 777));}}
观察执行日志可知,数据源的切换实现了 , 事务也没有失效 。推荐阅读
- 二 SpringBoot - 核心配置文件
- Nebula Graph介绍和SpringBoot环境连接和查询
- 怎样设置苹果手机铃声(ios自定义来电铃声)
- java常用注解校验参数
- springboot 多线程的使用
- springboot H2 linux下搭建使用
- AgileBoot - 基于SpringBoot + Vue3的前后端快速开发脚手架
- SpringBoot-JavaMailSender接口实战
- @Transactional注解真的有必要声明rollbackFor属性吗?
- 一 SpringBoot - SpringBoot 初识