Springboot-AOP自定义注解-双数据源
一.引入依赖
1 <!-- aop --> 2 <dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-aop</artifactId> 5 </dependency> 6 <!-- MySQL 连接驱动依赖 --> 7 <dependency> 8 <groupId>mysql</groupId> 9 <artifactId>mysql-connector-java</artifactId> 10 <version>8.0.26</version> 11 </dependency> 12 13 <!-- Druid 数据连接池依赖 --> 14 <dependency> 15 <groupId>com.alibaba</groupId> 16 <artifactId>druid-spring-boot-starter</artifactId> 17 <version>1.2.8</version> 18 </dependency> 19 20 <dependency> 21 <groupId>org.mybatis.spring.boot</groupId> 22 <artifactId>mybatis-spring-boot-starter</artifactId> 23 <version>2.1.4</version> 24 </dependency>
二.创建双数据库
我这里创建的是两个一样的表
1 /* 2 Navicat Premium Data Transfer 3 4 Source Server : myself 5 Source Server Type : MySQL 6 Source Server Version : 80023 7 Source Host : localhost:3306 8 Source Schema : tattoo 9 10 Target Server Type : MySQL 11 Target Server Version : 80023 12 File Encoding : 65001 13 14 Date: 23/11/2021 17:17:30 15 */ 16 17 SET NAMES utf8mb4; 18 SET FOREIGN_KEY_CHECKS = 0; 19 20 -- ---------------------------- 21 -- Table structure for user 22 -- ---------------------------- 23 DROP TABLE IF EXISTS `user`; 24 CREATE TABLE `user` ( 25 `id` int NOT NULL AUTO_INCREMENT, 26 `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 27 `pass_word` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 28 `age` int NULL DEFAULT NULL, 29 `sex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 30 `create_time` datetime(0) NULL DEFAULT NULL, 31 PRIMARY KEY (`id`) USING BTREE 32 ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; 33 34 SET FOREIGN_KEY_CHECKS = 1;
三.修改application.yml 配置信息
1 server: 2 port: 8089 3 4 spring: 5 datasource: 6 type: com.alibaba.druid.pool.DruidDataSource 7 driverClassName: com.mysql.cj.jdbc.Driver 8 druid: 9 primary: 10 url: jdbc:mysql://localhost:3306/tattoo?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 11 username: root 12 password: root 13 second: 14 url: jdbc:mysql://localhost:3306/tattoo2?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 15 username: root 16 password: root 17 18 mybatis: 19 mapper-locations: classpath:mapper/*.xml 20 type-aliases-package: com.example.doubledb.entity
项目整体结构
1.枚举类DataSourceName
该类用来存放数据源的名称,定义两个数据源名称分别为PRIMARY和SECOND。
1 package com.example.doubledb.enums; 2 3 /** 4 * @Author: GG 5 * @Date: 2021/11/23 15:37 6 */ 7 public enum DataSourceName { 8 9 /** 10 * 主数据源 tattoo 11 */ 12 PRIMARY("PRIMARY"), 13 14 /** 15 * 副数据源 tattoo2 16 */ 17 SECOND("SECOND"); 18 19 private String dataSourceName; 20 private DataSourceName(String dataSourceName){ 21 this.dataSourceName=dataSourceName; 22 } 23 DataSourceName(){ 24 25 } 26 public String getDataSourceName(){ 27 return this.dataSourceName; 28 } 29 }
2、配置类DynamicDataSourceConfig
通过@ConfigurationProperties读取配置文件中的数据源配置信息,并通过DruidDataSourceBuilder.create().build()创建数据连接,将多个数据源放入map,注入到IoC中:
1 package com.example.doubledb.config; 2 3 4 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; 5 import com.example.doubledb.bean.DynamicDataSource; 6 import com.example.doubledb.enums.DataSourceName; 7 import org.springframework.boot.context.properties.ConfigurationProperties; 8 import org.springframework.context.annotation.Bean; 9 import org.springframework.context.annotation.Configuration; 10 import org.springframework.context.annotation.Primary; 11 12 import javax.sql.DataSource; 13 import java.util.HashMap; 14 import java.util.Map; 15 16 /** 17 * @Author: GG 18 * @Date: 2021/11/23 15:38 19 */ 20 @Configuration 21 public class DynamicDataSourceConfig { 22 /** 23 * 创建DataSource Bean,将数据源配置从配置文件中读出 24 */ 25 26 @Bean 27 @ConfigurationProperties("spring.datasource.druid.primary") 28 public DataSource oneDataSource(){ 29 return DruidDataSourceBuilder.create().build(); 30 } 31 32 @Bean 33 @ConfigurationProperties("spring.datasource.druid.second") 34 public DataSource twoDataSource(){ 35 return DruidDataSourceBuilder.create().build(); 36 } 37 38 /** 39 * 将数据源放入到 这个map中,注入到IoC 40 */ 41 @Bean 42 @Primary 43 public DynamicDataSource dataSource(DataSource oneDataSource, DataSource twoDataSource){ 44 Map<Object,Object> targetDataSources=new HashMap<>(2); 45 targetDataSources.put(DataSourceName.PRIMARY.getDataSourceName(),oneDataSource); 46 targetDataSources.put(DataSourceName.SECOND.getDataSourceName(),twoDataSource); 47 return new DynamicDataSource(oneDataSource,targetDataSources); 48 } 49 }
3、动态数据源DynamicDataSource:
通过继承AbstractRoutingDataSource类,在构造函数中调用父类的方法,将配置类中放入map的数据源集合定为备选数据源,将传来的oneDataSource作为默认数据源:
1 package com.example.doubledb.bean; 2 3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 4 5 import javax.sql.DataSource; 6 import java.util.Map; 7 8 /** 9 * @Author: GG 10 * @Date: 2021/11/23 15:39 11 */ 12 public class DynamicDataSource extends AbstractRoutingDataSource { 13 private static final ThreadLocal<String> contextHolder=new ThreadLocal<>(); 14 /** 15 * 配置DataSource 16 * 设置defaultTargetDataSource为主数据库 17 */ 18 public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object,Object> targetDataSources){ 19 super.setDefaultTargetDataSource(defaultTargetDataSource); 20 super.setTargetDataSources(targetDataSources); 21 super.afterPropertiesSet(); 22 } 23 public static String getDataSource(){ 24 return contextHolder.get(); 25 } 26 public static void setDataSource(String dataSource){ 27 contextHolder.set(dataSource); 28 } 29 public static void clearDataSource(){ 30 contextHolder.remove(); 31 } 32 33 @Override 34 protected Object determineCurrentLookupKey() { 35 return getDataSource(); 36 } 37 }
setTargetDataSources设置备选的数据源集合,
setDefaultTargetDataSource设置默认数据源,
determineCurrentLookupKey决定当前数据源的对应的key。
4、自定义注释类DataSource:
1 package com.example.doubledb.annotation; 2 3 4 import com.example.doubledb.enums.DataSourceName; 5 import java.lang.annotation.*; 6 /** 7 * @Author: GG 8 * @Date: 2021/11/23 15:40 9 */ 10 11 @Target({ElementType.METHOD, ElementType.TYPE}) 12 @Retention(RetentionPolicy.RUNTIME) 13 @Documented 14 @Inherited 15 public @interface DataSource { 16 DataSourceName value() default DataSourceName.PRIMARY; 17 }
@Documented指定被标注的注解会包含在javadoc中,
@Target指定注释可能出现在Java程序中的语法位置(ElementType.METHOD则说明注解可能出现在方法上 ,ElementType.TYPE 指定可能出现在类上)
@Retention指定注释的保留时间(RetentionPolicy.RUNTIME则是在java文件编译成class类时也依旧保存该注释)。
5、切面类DataSourceAspect:
1 package com.example.doubledb.aspect; 2 3 4 import com.example.doubledb.annotation.DataSource; 5 import com.example.doubledb.bean.DynamicDataSource; 6 import org.aspectj.lang.ProceedingJoinPoint; 7 import org.aspectj.lang.annotation.Around; 8 import org.aspectj.lang.annotation.Aspect; 9 import org.aspectj.lang.annotation.Pointcut; 10 import org.aspectj.lang.reflect.MethodSignature; 11 import org.slf4j.Logger; 12 import org.slf4j.LoggerFactory; 13 import org.springframework.core.Ordered; 14 import org.springframework.stereotype.Component; 15 16 import java.lang.reflect.Method; 17 18 /** 19 * @Author: GG 20 * @Date: 2021/11/23 15:40 21 */ 22 @Aspect 23 @Component 24 public class DataSourceAspect implements Ordered { 25 private Logger log= LoggerFactory.getLogger(DataSourceAspect.class); 26 27 /** 28 * 切点:所有配置DataSource注解的方法 29 */ 30 @Pointcut("@annotation(com.example.doubledb.annotation.DataSource)") 31 public void dataSourcePointCut(){ 32 33 } 34 35 @Around(value = "dataSourcePointCut()") 36 public Object around(ProceedingJoinPoint point) throws Throwable{ 37 Object result; 38 MethodSignature signature=(MethodSignature)point.getSignature(); 39 Method method=signature.getMethod(); 40 DataSource ds=method.getAnnotation(DataSource.class); 41 /** 42 * 判断DataSource的值 43 * 获取当前方法应用的数据源 44 */ 45 DynamicDataSource.setDataSource(ds.value().getDataSourceName()); 46 try{ 47 result=point.proceed(); 48 }finally { 49 DynamicDataSource.clearDataSource(); 50 } 51 return result; 52 } 53 54 @Override 55 public int getOrder() { 56 return 1; 57 } 58 }
Spring框架有很多相同接口的实现类,提供了Ordered接口来处理相同接口实现类之间的优先级问题。
通过环绕切面,对方法上的注释进行了检验,如果获取到有DataSource注释,则会进行数据源的切换,否则按默认数据源进行处理。
6、引入配置类:
既然手动配置了动态切换数据连接池,就要在入口类中排除自动引入,并引入数据源的配置类,以及开启AOP:
1 package com.example.doubledb; 2 3 import com.example.doubledb.config.DynamicDataSourceConfig; 4 import org.mybatis.spring.annotation.MapperScan; 5 import org.springframework.boot.SpringApplication; 6 import org.springframework.boot.autoconfigure.SpringBootApplication; 7 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 8 import org.springframework.context.annotation.EnableAspectJAutoProxy; 9 import org.springframework.context.annotation.Import; 10 11 /** 12 * @author Administrator 13 */ 14 @MapperScan(basePackages ="com.example.doubledb.mapper") 15 @Import({DynamicDataSourceConfig.class}) 16 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) 17 @EnableAspectJAutoProxy 18 public class SpringbootDoubleDbApplication { 19 20 public static void main(String[] args) { 21 SpringApplication.run(SpringbootDoubleDbApplication.class, args); 22 } 23 24 }
7.controller 类注解使用主数据源
1 package com.example.doubledb.controller; 2 3 import com.example.doubledb.annotation.DataSource; 4 import com.example.doubledb.entity.User; 5 import com.example.doubledb.enums.DataSourceName; 6 import com.example.doubledb.service.UserService; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.web.bind.annotation.GetMapping; 9 import org.springframework.web.bind.annotation.RestController; 10 11 /** 12 * @Author: GG 13 * @Date: 2021/11/23 11:49 14 */ 15 @DataSource(DataSourceName.PRIMARY) 16 @RestController 17 public class UserController { 18 private UserService userService; 19 @Autowired 20 public UserController(UserService userService) { 21 this.userService = userService; 22 } 23 24 @GetMapping("/test") 25 public User queryUser(int id){ 26 return userService.queryUser(id); 27 } 28 }