• SpringBoot框架:通过AOP和自定义注解完成druid连接池的动态数据源切换(三)


    一、引入依赖

      引入数据库连接池的依赖——druid和面向切面编程的依赖——aop,如下所示:

            <!-- druid -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.21</version>
            </dependency>
            <!-- aop -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>

    二、创建数据库

      1、主数据库

      使用前文中已经创建的名为spring_boot_demo的数据库。

      spring_boot_demo中t_user数据如下:

      

      2、辅数据库

      数据库名为other_data,库中建立数据表t_user,表结构与spring_boot_demo中的t_user一致。

      实际项目中,大多是跨数据库的数据源切换,常用在同公司的多个不同系统中共用一个用户数据库,或者二次开发项目在原有数据库基础上做拓展,保留原有的数据连接。

      这里为了方便操作,就都在mysql下部署数据库并且使表结构一致,方便形成数据对比。

      other_data中插入数据如下:

      

    三、修改数据库连接配置信息

      在application.yml中,修改数据库连接配置如下:

    spring:
      application:
        name: spring-boot-demo
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
          primary:
            url: jdbc:mysql://localhost:3306/spring_boot_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8
            username: root
            password: root
            driverClassName: com.mysql.jdbc.Driver
          second:
            url: jdbc:mysql://localhost:3306/other_data?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8
            username: root
            password: root
            driverClassName: com.mysql.jdbc.Driver

      

    四、编写代码

      结构如下:

      

      1、枚举类DataSourceName:

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

    package com.example.demo.enums;
    
    /**
     * DataSource的name常量
     * 便于切换
     * @author 我命倾尘
     */
    public enum DataSourceName {
    
        /**
         * 主数据源 spring_boot_demo
         */
        PRIMARY("PRIMARY"),
    
        /**
         * 副数据源other_data
         */
        SECOND("SECOND");
    
        private String dataSourceName;
        private DataSourceName(String dataSourceName){
            this.dataSourceName=dataSourceName;
        }
        DataSourceName(){
    
        }
        public String getDataSourceName(){
            return this.dataSourceName;
        }
    }

      2、配置类DynamicDataSourceConfig:

      通过@ConfigurationProperties读取配置文件中的数据源配置信息,并通过DruidDataSourceBuilder.create().build()创建数据连接,将多个数据源放入map,注入到IoC中:

    package com.example.demo.config;
    
    import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
    import com.example.demo.bean.DynamicDataSource;
    import com.example.demo.enums.DataSourceName;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author 我命倾尘
     */
    @Configuration
    public class DynamicDataSourceConfig {
        /**
         * 创建DataSource Bean,将数据源配置从配置文件中读出
         */
    
        @Bean
        @ConfigurationProperties("spring.datasource.druid.primary")
        public DataSource oneDataSource(){
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean
        @ConfigurationProperties("spring.datasource.druid.second")
        public DataSource twoDataSource(){
            return DruidDataSourceBuilder.create().build();
        }
    
        /**
         * 将数据源放入到 这个map中,注入到IoC
         */
        @Bean
        @Primary
        public DynamicDataSource dataSource(DataSource oneDataSource, DataSource twoDataSource){
            Map<Object,Object> targetDataSources=new HashMap<>(2);
            targetDataSources.put(DataSourceName.PRIMARY.getDataSourceName(),oneDataSource);
            targetDataSources.put(DataSourceName.SECOND.getDataSourceName(),twoDataSource);
            return new DynamicDataSource(oneDataSource,targetDataSources);
        }
    }

      3、动态数据源DynamicDataSource:

      通过继承AbstractRoutingDataSource类,在构造函数中调用父类的方法,将配置类中放入map的数据源集合定为备选数据源,将传来的oneDataSource作为默认数据源

    package com.example.demo.bean;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    import javax.sql.DataSource;
    import java.util.Map;
    
    /**
     * @author 我命倾尘
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        private static final ThreadLocal<String> contextHolder=new ThreadLocal<>();
        /**
         * 配置DataSource
         * 设置defaultTargetDataSource为主数据库
         */
        public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object,Object> targetDataSources){
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            super.setTargetDataSources(targetDataSources);
            super.afterPropertiesSet();
        }
        public static String getDataSource(){
            return contextHolder.get();
        }
        public static void setDataSource(String dataSource){
            contextHolder.set(dataSource);
        }
        public static void clearDataSource(){
            contextHolder.remove();
        }
    
        @Override
        protected Object determineCurrentLookupKey() {
            return getDataSource();
        }
    }

      setTargetDataSources设置备选的数据源集合,

      setDefaultTargetDataSource设置默认数据源,

      determineCurrentLookupKey决定当前数据源的对应的key

      4、自定义注释类DataSource:

    package com.example.demo.annotation;
    
    import com.example.demo.enums.DataSourceName;
    
    import java.lang.annotation.*;
    
    /**
     * @author 我命倾尘
     */
    @Documented
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DataSource {
        DataSourceName value() default DataSourceName.PRIMARY;
    }

      @Documented指定被标注的注解会包含在javadoc中,

      @Target指定注释可能出现在Java程序中的语法位置(ElementType.METHOD则说明注解可能出现在方法上),

      @Retention指定注释的保留时间(RetentionPolicy.RUNTIME则是在java文件编译成class类时也依旧保存该注释)。

      5、切面类DataSourceAspect:

    package com.example.demo.aspect;
    
    import com.example.demo.annotation.DataSource;
    import com.example.demo.bean.DynamicDataSource;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.Ordered;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    
    /**
     * @author 我命倾尘
     */
    @Aspect
    @Component
    public class DataSourceAspect implements Ordered {
        private Logger log= LoggerFactory.getLogger(DataSourceAspect.class);
    
        /**
         * 切点:所有配置DataSource注解的方法
         */
        @Pointcut("@annotation(com.example.demo.annotation.DataSource)")
        public void dataSourcePointCut(){
    
        }
    
        @Around(value = "dataSourcePointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable{
            Object result;
            MethodSignature signature=(MethodSignature)point.getSignature();
            Method method=signature.getMethod();
            DataSource ds=method.getAnnotation(DataSource.class);
            /**
             * 判断DataSource的值
             * 获取当前方法应用的数据源
             */
            DynamicDataSource.setDataSource(ds.value().getDataSourceName());
            try{
                result=point.proceed();
            }finally {
                DynamicDataSource.clearDataSource();
            }
            return result;
        }
    
        @Override
        public int getOrder() {
            return 1;
        }
    }

      Spring框架有很多相同接口的实现类,提供了Ordered接口来处理相同接口实现类之间的优先级问题。

      通过环绕切面,对方法上的注释进行了检验,如果获取到有DataSource注释,则会进行数据源的切换,否则按默认数据源进行处理。

      6、引入配置类:

      既然手动配置了动态切换数据连接池,就要在入口类中排除自动引入,并引入数据源的配置类,以及开启AOP:

    package com.example.demo;
    
    import com.example.demo.config.DynamicDataSourceConfig;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    import org.springframework.context.annotation.Import;
    
    @MapperScan("com.example.demo.mapper")
    @Import({DynamicDataSourceConfig.class})
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    @EnableAspectJAutoProxy
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }

      通过@Import引入配置类,在@SpringBootApplication后进行自动引入的排除。

      @EnableAspectJAutoProxy用来开启AOP。

    五、简单测试

      1、不使用注解:

      UserController中的方法如下:

      @RequestMapping("/user/age")
      public int getAgeOfUser(){
            return userService.getAgeByUsername("springbootdemo");
      }

      所得到的结果如下:

      

      这个结果是从主数据源spring_boot_demo数据库的表中得到的数据。

      2、在方法前添加注解@DataSource(DataSourceName.SECOND)

      UserController中的方法如下:

      @RequestMapping("/user/age")
      @DataSource(DataSourceName.SECOND)
      public int getAgeOfUser(){
            return userService.getAgeByUsername("springbootdemo");
      }

      结果如下:

      

      这个结果则是从辅数据源other_data中得到的数据。

      前言:SpringBoot框架:使用mybatis连接mysql数据库完成数据访问(二)

  • 相关阅读:
    exec系列函数和system函数
    fork函数相关总结
    文件的内核结构file和dup实现重定向
    进程基本概述
    fcntl 函数与文件锁
    文件的属性
    目录的操作
    文件的读取写入
    文件的打开关闭
    浅谈原始套接字 SOCK_RAW 的内幕及其应用(port scan, packet sniffer, syn flood, icmp flood)
  • 原文地址:https://www.cnblogs.com/guobin-/p/13696900.html
Copyright © 2020-2023  润新知