zoukankan      html  css  js  c++  java
  • SpringBoot与动态多数据源切换

     

     

    本文简单的介绍一下基于SpringBoot框架动态多数据源切换的实现,采用主从配置的方式,配置master、slave两个数据库。

    一、配置主从数据库

    复制代码
    spring:
        datasource:
            type: com.alibaba.druid.pool.DruidDataSource
            driverClassName: com.mysql.cj.jdbc.Driver
            druid:
                # 主库数据源
                master:
                    url: jdbc:mysql://localhost:3306/practice?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                    username: root
                    password: 123456
                # 从库数据源
                slave:
                    # 从数据源开关/默认关闭
                    enabled: true
                    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                    username: root
                    password: 123456
    复制代码

    二、创建数据源枚举类

    复制代码
    public enum DataSourceType {
        /**
         * 主库
         */
        MASTER,
    
        /**
         * 从库
         */
        SLAVE
    }
    复制代码

    三、数据源切换处理

      创建一个数据源切换处理类,有对数据源变量的获取、设置和清空的方法。其中的ThreadLocal用于保存某个线程共享变量。详细的ThreadLocal的相关了解,可以查看地址:https://www.cnblogs.com/slivelove/p/10950527.html

    复制代码
    public class DynamicDataSourceContextHolder {
        public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
    
        /**
         * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
         *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
         */
        private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    
        /**
         * 设置数据源变量
         * @param dataSourceType
         */
        public static void setDataSourceType(String dataSourceType){
            log.info("切换到{}数据源", dataSourceType);
            CONTEXT_HOLDER.set(dataSourceType);
        }
    
        /**
         * 获取数据源变量
         * @return
         */
        public static String getDataSourceType(){
            return CONTEXT_HOLDER.get();
        }
    
        /**
         * 清空数据源变量
         */
        public static void clearDataSourceType(){
            CONTEXT_HOLDER.remove();
        }
    }
    复制代码

    四、继承AbstractRoutingDataSource

      动态切换数据源主要依靠AbstractRoutingDataSource。创建一个AbstractRoutingDataSource的子类,重写determineCurrentLookupKey方法,用于决定使用哪一个数据源。这里主要用到AbstractRoutingDataSource的两个属性defaultTargetDataSource和targetDataSources。defaultTargetDataSource默认目标数据源,targetDataSources(map类型)存放用来切换的数据源。

    复制代码
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            super.setTargetDataSources(targetDataSources);
            // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
            super.afterPropertiesSet();
        }
    
        /**
         * 根据Key获取数据源的信息
         *
         * @return
         */
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceContextHolder.getDataSourceType();
        }
    }
    复制代码

    五、注入数据源

    复制代码
    @Configuration
    public class DataSourceConfig {
        @Bean
        @ConfigurationProperties("spring.datasource.druid.master")
        public DataSource masterDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean
        @ConfigurationProperties("spring.datasource.druid.slave")
        @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
        public DataSource slaveDataSource() {
            return DataSourceBuilder.create().build();
        }
        
        @Bean(name = "dynamicDataSource")
        @Primary
        public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
            targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
            return new DynamicDataSource(masterDataSource, targetDataSources);
        }
    }
    复制代码

    六、自定义多数据源切换注解

      设置拦截数据源的注解,可以设置在具体的类上,或者在具体的方法上。

    复制代码
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSource {
        /**
         * 切换数据源名称
         */
        DataSourceType value() default DataSourceType.MASTER;
    }
    复制代码

    七、AOP拦截类的实现

      通过拦截上面的注解,在其执行之前处理设置当前执行SQL的数据源的信息,CONTEXT_HOLDER.set(dataSourceType)这里的数据源信息从我们设置的注解上面获取信息,如果没有设置就是用默认的数据源的信息。

    复制代码
    @Aspect
    @Order(1)
    @Component
    public class DataSourceAspect {
        private Logger log = LoggerFactory.getLogger(getClass());
    
        @Pointcut("@annotation(com.wlfu.common.annotation.DataSource)")
        public void dsPointCut() {
    
        }
    
        @Around("dsPointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            DataSource dataSource = method.getAnnotation(DataSource.class);
            if (dataSource != null) {
                DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
            }
            try {
                return point.proceed();
            } finally {
                // 销毁数据源 在执行方法之后
                DynamicDataSourceContextHolder.clearDataSourceType();
            }
        }
    }
    复制代码

      这里使用@Around,在调用目标方法前,进行aop拦截,通过解析注解上的值来切换数据源。在调用方法结束后,清除数据源。也可以使用@Before和@After来编写,原理一样,这里就不多说了。

    八、使用切换数据源注解

      设置拦截数据源的注解,可以设置在具体的类上,或者在具体的方法上。

    复制代码
    @DataSource(value = DataSourceType.SLAVE)
    @PostMapping("/list")
    @ResponseBody
    public TableDataInfo list() {
        
    }
  • 相关阅读:
    根据用户输入的时间查询那天的数据
    动软 生成 linq相关DAO
    pdf 移除密码 去除水印 批量去除水印 编辑文字 批量替换文字
    利用OCR识别扫描的jpg、tif文件的文字
    jstat命令详解
    IDEA自动编译设置
    IntelliJ IDEA:Field injection is not recommended
    阿里巴巴Druid数据库连接池配置详解及使用
    com.mysql.jdbc.Driver和com.mysql.cj.jdbc.Driver的区别
    Java对元与分的金额的转换
  • 原文地址:https://www.cnblogs.com/wcss/p/12374052.html
Copyright © 2011-2022 走看看