• 基于SpringBoot+Druid实现多数据源:注解+编程式


    前言

    本博客姊妹篇

    一、功能描述

    • 配置方式:配置文件中配置默认数据源,使用数据库存储其他数据源配置
    • 使用方式:使用注解切换数据源,编程式切换数据源

    二、代码实现

    2.1 配置

    # spring配置
    spring:
      # 数据源配置
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/boot_codegen?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8&useSSL=false
          username: root
          password: root
          initial-size: 10
          min-idle: 10
          max-active: 100
          max-wait: 60000
          time-between-eviction-runs-millis: 60000
          min-evictable-idle-time-millis: 300000
          validation-query: select 1
          test-while-idle: true
          test-on-borrow: false
          test-on-return: false
          pool-prepared-statements: true
          max-pool-prepared-statement-per-connection-size: 20
          web-stat-filter:
            enabled: true
            url-pattern: /*
            exclusions: '*.js,*.css,*.gif,*.png,*.jpg,*.ico,/druid/*'
          stat-view-servlet:
            enabled: true
            url-pattern: /druid/*
            login-username: admin
            login-password: 123456
          filter:
            stat:
              enabled: true
              log-slow-sql: true
              slow-sql-millis: 1000
              merge-sql: true
            wall:
              enabled: true
              config:
                multi-statement-allow: true
    
    • 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

    2.2 配置类

    package com.qiangesoft.datasourcepro.core;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    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 qiangesoft
     * @date 2024-03-14
     */
    @Slf4j
    @Configuration
    public class DataSourceConfiguration {
    
        @Bean
        public DruidDataSource defaultDataSource() {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean
        @Primary
        public DynamicDataSource dynamicDataSource(DruidDataSource defaultDataSource) {
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put("default", defaultDataSource);
            return new DynamicDataSource(defaultDataSource, targetDataSources);
        }
    }
    
    
    
    • 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

    2.3 多数据源扩展实现

    package com.qiangesoft.datasourcepro.core;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.qiangesoft.datasourcepro.entity.BcgDataSource;
    import com.qiangesoft.datasourcepro.service.IBcgDataSourceService;
    import com.qiangesoft.datasourcepro.utils.SpringUtil;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    import java.util.Map;
    
    /**
     * 动态数据源
     *
     * @author qiangesoft
     * @date 2024-03-14
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        private Map<Object, Object> dynamicTargetDataSources;
    
        private Object dynamicDefaultTargetDataSource;
    
        public DynamicDataSource(DruidDataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            super.setTargetDataSources(targetDataSources);
            super.afterPropertiesSet();
    
            this.dynamicTargetDataSources = targetDataSources;
            this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
        }
    
        @Override
        protected Object determineCurrentLookupKey() {
            String datasourceId = DataSourceContext.getDataSource();
            if (datasourceId != null && this.dynamicTargetDataSources.containsKey(datasourceId)) {
                return datasourceId;
            }
            return null;
        }
    
        /**
         * 添加数据源
         *
         * @param dataSourceId
         */
        public void addDataSource(String dataSourceId) {
            if ("default".equals(dataSourceId)) {
                return;
            }
    
            int initialSize = ((DruidDataSource) dynamicDefaultTargetDataSource).getInitialSize();
    
            BcgDataSource bcgDataSource = SpringUtil.getBean(IBcgDataSourceService.class).getById(Long.valueOf(dataSourceId));
            if (bcgDataSource == null) {
                throw new RuntimeException("数据源配置不存在");
            }
    
            String datasourceId = String.valueOf(bcgDataSource.getId());
            Map<Object, Object> dynamicTargetDataSources = this.dynamicTargetDataSources;
            if (!dynamicTargetDataSources.containsKey(datasourceId)) {
                DruidDataSource dataSourceInstance = DataSourceBuilder.create()
                        .driverClassName(bcgDataSource.getDriverClassName())
                        .url(bcgDataSource.getUrl())
                        .username(bcgDataSource.getUsername())
                        .password(bcgDataSource.getPassword())
                        .type(DruidDataSource.class)
                        .build();
                dynamicTargetDataSources.put(datasourceId, dataSourceInstance);
                // 关键一步:将TargetDataSources中的连接信息放入resolvedDataSources管理
                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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    2.4 切面

    package com.qiangesoft.datasourcepro.core;
    
    import lombok.extern.slf4j.Slf4j;
    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.springframework.core.annotation.AnnotationUtils;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import java.util.Objects;
    
    /**
     * 多数据源处理
     *
     * @author qiangesoft
     * @date 2024-03-14
     */
    @Slf4j
    @Order(1)
    @Aspect
    @Component
    public class DataSourceAspect {
    
        /**
         * 切点
         */
        @Pointcut("@annotation(com.qiangesoft.datasourcepro.core.DataSource)")
        public void pointCut() {
        }
    
        /**
         * 通知
         *
         * @param joinPoint
         * @return
         * @throws Throwable
         */
        @Around("pointCut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            DataSource dataSource = this.getDataSource(joinPoint);
            if (dataSource == null) {
                DataSourceContext.setDataSource("default");
            } else {
                DataSourceContext.setDataSource(dataSource.value());
            }
            try {
                return joinPoint.proceed();
            } finally {
                DataSourceContext.removeDataSource();
            }
        }
    
        /**
         * 获取数据源
         *
         * @param joinPoint
         * @return
         */
        public DataSource getDataSource(ProceedingJoinPoint joinPoint) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 方法上查找注解
            DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
            if (Objects.nonNull(dataSource)) {
                return dataSource;
            }
            // 类上查找注解
            return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
        }
    }
    
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    2.5 线程本地变量

    package com.qiangesoft.datasourcepro.core;
    
    import com.qiangesoft.datasourcepro.utils.SpringUtil;
    
    /**
     * 数据源上下文
     *
     * @author qiangesoft
     * @date 2024-03-14
     */
    public class DataSourceContext {
    
        /**
         * 线程本地变量:数据源
         */
        private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    
        /**
         * 设置数据源的变量
         */
        public static void setDataSource(String dataSourceId) {
            SpringUtil.getBean(DynamicDataSource.class).addDataSource(dataSourceId);
            CONTEXT_HOLDER.set(dataSourceId);
        }
    
        /**
         * 获得数据源的变量
         */
        public static String getDataSource() {
            return CONTEXT_HOLDER.get();
        }
    
        /**
         * 清空数据源变量
         */
        public static void removeDataSource() {
            CONTEXT_HOLDER.remove();
        }
    }
    
    • 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

    2.6 使用

    package com.qiangesoft.datasourcepro.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.qiangesoft.datasourcepro.core.DataSource;
    import com.qiangesoft.datasourcepro.core.DataSourceContext;
    import com.qiangesoft.datasourcepro.entity.BcgDataSource;
    import com.qiangesoft.datasourcepro.mapper.BcgDataSourceMapper;
    import com.qiangesoft.datasourcepro.service.IBcgDataSourceService;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * 

    * 数据源 服务实现类 *

    * * @author qiangesoft * @since 2024-03-13 */
    @Slf4j @RequiredArgsConstructor @Service public class BcgBcgDataSourceServiceImpl extends ServiceImpl<BcgDataSourceMapper, BcgDataSource> implements IBcgDataSourceService { @Override public List<BcgDataSource> changeDataSource(String datasourceId) { try { DataSourceContext.setDataSource(datasourceId); return this.list(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("数据源切换失败"); } finally { DataSourceContext.removeDataSource(); } } @DataSource(value = "1") @Override public List<BcgDataSource> changeDataSourceByAnnotation() { return this.list(); } }
    • 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
  • 相关阅读:
    通信工程学习:什么是接入网(AN)中的CF核心功能
    【linux命令讲解大全】086.终端会话重现工具scriptreplay的使用方法详解
    鸿蒙 gnss 发起定位流程
    OpenAI抓内鬼出奇招,奥特曼耍了所有人:GPT搜索鸽了!改升级GPT-4
    Partial derivative
    如何修改运行中的docker容器的端口映射
    Utrust4701F与Utrust2700R读写器在银河麒麟系统的安装及测试步骤说明
    硕士论文章节划分
    数控车床中滚珠螺母的维护保养方法
    [kaldi] alignment 对齐 (音素级和词级)
  • 原文地址:https://blog.csdn.net/weixin_39311781/article/details/136722598