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

该类用来存放数据源的名称,定义两个数据源名称分别为PRIMARYSECOND

 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 }

 

posted @ 2021-11-23 17:34  RunawayProgrammer  阅读(286)  评论(0编辑  收藏  举报