• SpringBoot实现多数据源(三)【AOP + 自定义注解】


    上一篇文章SpringBoot实现多数据源(二)【Mybatis插件】

    三、通过 AOP + 自定义注解切换多数据源


    适用范围:不同场景下的业务,一般利用AOP,结合自定义注解动态切换数据源

    1. 配置文件application.yml不变,导入依赖
      • pom.xml
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-aopartifactId>
        dependency>
        
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.1.4version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druid-spring-boot-starterartifactId>
            <version>1.2.8version>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.28version>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.18.24version>
        dependency>
    
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    1. 自定义读写分离注解
      • @ReadAndWrite
    package com.vinjcent.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.METHOD, ElementType.TYPE}) // 可以声明在哪些作用域上
    @Retention(RetentionPolicy.RUNTIME)
    /**
     * SOURCE、CLASS、RUNTIME:
     * SOURCE: 编译之后不会在target文件的.class中生成,无法通过反射获取
     * CLASS: 会保留在.class中,不会被jvm加载
     * RUNTIME: 运行时生效
     *
     */
    public @interface ReadAndWrite {
        String value() default "write";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 配置切面
      • DynamicDataSourceAspect
    package com.vinjcent.aspect;
    
    import com.vinjcent.annotation.ReadAndWrite;
    import com.vinjcent.config.DynamicDataSource;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    /**
     * 动态数据源切面
     */
    @Component
    @Aspect
    public class DynamicDataSourceAspect {
    
        // 前置通知before或环绕通知Around都可以
    
        // @annotation(rw)为所有ReadAndWrite注解进行增强
        // within(com.vinjcent.service.impl.*) 某个包下的类
        @Before("within(com.vinjcent.service.impl.*) && @annotation(rw)")
        public void before(JoinPoint point, ReadAndWrite rw) {
            // 获取当前注解中的value值
            String opt = rw.value();
            // 根据opt修改ThreadLocal中name的值,更换动态数据源
            DynamicDataSource.name.set(opt);
            System.out.println(opt);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    1. 配置读写分离数据源
      • DataSourceConfiguration
    package com.vinjcent.config;
    
    import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.sql.DataSource;
    
    @Configuration
    public class DataSourceConfiguration {
    
        @Bean(name = "readDatasource")
        @ConfigurationProperties(prefix = "spring.datasource.read")
        public DataSource readDatasource() {
            // 底层会自动拿到spring.datasource中的配置,创建一个DruidDataSource
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean(name = "writeDatasource")
        @ConfigurationProperties(prefix = "spring.datasource.write")
        public DataSource writeDatasource() {
            // 底层会自动拿到spring.datasource中的配置,创建一个DruidDataSource
            return DruidDataSourceBuilder.create().build();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. 动态数据源配置
      • DynamicDataSource
    package com.vinjcent.config;
    
    import com.vinjcent.constants.DataSourceConstants;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Primary;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    import org.springframework.stereotype.Component;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    
    @Component
    @Primary    // 将该Bean设置为主要注入Bean
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        // 用于存储数据源的标识
        public static ThreadLocal<String> name = new ThreadLocal<>();
    
        // 写
        private final DataSource writeDataSource;
    
        // 读
        private final DataSource readDataSource;
    
    
        @Autowired
        public DynamicDataSource(@Qualifier("readDatasource") DataSource readDataSource,
                                 @Qualifier("writeDatasource") DataSource writeDataSource) {
            this.readDataSource = readDataSource;
            this.writeDataSource = writeDataSource;
        }
    
        @Override
        protected Object determineCurrentLookupKey() {
            return name.get();
        }
    
        // 初始化完bean之后调用该方法
        @Override
        public void afterPropertiesSet() {
            // 为targetDataSources 初始化所有数据源
            Map<Object, Object> sources = new HashMap<>();
            sources.put(DataSourceConstants.READ_DATASOURCE, readDataSource);
            sources.put(DataSourceConstants.WRITE_DATASOURCE, writeDataSource);
            super.setTargetDataSources(sources);
    
            // 为 defaultTargetDataSource 设置默认的数据源
            super.setDefaultTargetDataSource(readDataSource);
    
            // resolvedDataSources 负责最终切换的数据源map
            super.afterPropertiesSet();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    1. Controller 层不变,修改impl中的 Service 层(这里就不再提供entity以及interface,与前面相同
    package com.vinjcent.service.impl;
    
    import com.vinjcent.annotation.ReadAndWrite;
    import com.vinjcent.mapper.PeopleMapper;
    import com.vinjcent.pojo.People;
    import com.vinjcent.service.PeopleService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class PeopleServiceImpl implements PeopleService {
    
        private final PeopleMapper peopleMapper;
    
        @Autowired
        public PeopleServiceImpl(PeopleMapper peopleMapper) {
            this.peopleMapper = peopleMapper;
        }
    
        @ReadAndWrite("read")
        @Override
        public List<People> list() {
            return peopleMapper.list();
        }
    
        @ReadAndWrite("write")
        @Override
        public boolean save(People people) {
            return peopleMapper.save(people);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    1. 运行并测试接口

    下一篇文章SpringBoot实现多数据源(四)【集成多个 Mybatis 框架】》

  • 相关阅读:
    Docker 平台架构&底层原理二
    一文速学-Base64算法及编解码方法+Python代码
    视觉里程计-代码仓库
    【数据结构教程】线性表及其逻辑结构
    OpenGL - SSAO
    【MATLAB源码-第42期】基于matlab的人民币面额识别系统(GUI)。
    provide/inject 依赖注入
    【自动驾驶】浅谈自动驾驶在业界的发展
    公共经济学(开卷)期末复习题
    17 结构型模式-享元模式
  • 原文地址:https://blog.csdn.net/Wei_Naijia/article/details/128069769