首页 > 其他分享 >SpringBoot配置动态数据源 原理+实战

SpringBoot配置动态数据源 原理+实战

时间:2024-08-28 15:53:54浏览次数:8  
标签:实战 return SpringBoot 数据源 DataSource key public datasource

若没空探究原理可直接跳转到“实现方式:注解+切面”目录

数据源切换方法

Spring对数据源的管理类似于策略模式,不懂策略模式也没关系,其实就是有一个全局的键值对,类型是Map<String, DataSource>。当JDBC操作数据库之时,会根据不同的key值选择不同的数据源。而这个key值可以放到方法的注解里。

所以切换数据源的思路就是让JDBC在获取数据源时根据key获取到要切换的数据源。

JDBC提供了AbstractRoutingDataSource抽象类,类名意思是数据源路由,该类提供了一个抽象方法determineCurrentLookupKey(),切换数据源时JDBC会调用这个方法获取数据源的key,所以只需要实现该方法,改变该方法中返回的key值即可。

源码解读

1.从类关系图中可以看出AbstractRoutingDataSource类实现了DataSource接口,后者有两个getConnection()方法,即获取DB连接的作用。

2.AbstractRoutingDataSource实现了这两个方法

其中determineTargetDataSource()方法的作用就是获取实际的数据源,其内部调用了determineCurrentLookupKey()方法,取到当前设定的key,通过key在上下文this.resolvedDataSources属性中尝试获取DataSource对象,这个对象即当前连接的数据源

3.那么this.resolvedDataSources在哪里维护呢? 继续在AbstractRoutingDataSource类里找,可以找到afterPropertiesSet()方法,这个方法是InitializingBean接口的,作用是在bean的所有属性设置完成后便会调用此方法。可以看到this.resolvedDataSources是从this.targetDataSources取的信息。

所以只需要改变this.targetDataSources,即可改变this.resolvedDataSources;后续改变determineCurrentLookupKey()的返回值(key),在调用getConnection()时即可获取到指定的数据源

实现方式:注解+切面

别看步骤挺多,但其实很容易理解和使用

1.配置文件示例:

spring:
  datasource: 
    master: # 数据源1
      jdbc-url: jdbc:mysql://localhost:3306/master?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    db1: # 数据源1
      jdbc-url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

2.创建数据源配置类

创建数据源配置类(我这里为了方便区分就为每一个数据源创建了一个配置类,当然也可以把所有的数据源配置在一个类里)

@Configuration
@EnableConfigurationProperties({MasterDataSourceProperties.class})
public class MasterDataSourceConfig {
    /**
    * 这个MasterDataSourceProperties是读取配置文件的类,我这里为了省篇幅就不展示了
    **/
    @Autowired
    private MasterDataSourceProperties masterDataSourceProperties;

    @Bean
    @Primary
    public DataSource masterDataSource() {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(masterDataSourceProperties.getUrl());
        datasource.setUsername(masterDataSourceProperties.getUsername());
        datasource.setPassword(AESUtil.aesDecode(masterDataSourceProperties.getPassword()));
        datasource.setDriverClassName(masterDataSourceProperties.getDriverClassName());
        ......

        return datasource;
    }
}
@Configuration
@EnableConfigurationProperties({OdsDataSourceProperties.class})
public class DB1DataSourceConfig {
    /**
    * 这个DB1DataSourceProperties是读取配置文件的类,我这里为了省篇幅就不展示了
    **/
    @Autowired
    private DB1DataSourceProperties dB1DataSourceProperties;

    @Bean
    public DataSource db1DataSource() {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(dB1DataSourceProperties.getUrl());
        datasource.setUsername(dB1DataSourceProperties.getUsername());
        datasource.setPassword(AESUtil.aesDecode(dB1DataSourceProperties.getPassword()));
        datasource.setDriverClassName(dB1DataSourceProperties.getDriverClassName());
        ......

        return datasource;
    }
}

3.创建DynamicDataSource

创建自己的一个DynamicDataSource类(名字任意)继承AbstractRoutingDataSource,维护数据源,提供切换方法。

public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
    * 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
    * 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
    */
    @Override
    protected DataSource determineTargetDataSource() {
        return super.determineTargetDataSource();
    }

    /**
    * 如果希望所有数据源在启动配置时就加载好,然后通过设置数据源Key值来切换数据,定制这个方法
    */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }

    /**
    * 设置默认数据源
    *
    * @param defaultDataSource
    */
    public void setDefaultDataSource(Object defaultDataSource) {
        super.setDefaultTargetDataSource(defaultDataSource);
    }

    /**
    * 设置数据源
    *
    * @param dataSources
    */
    public void setDataSources(Map<Object, Object> dataSources) {
        super.setTargetDataSources(dataSources);
        // 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
        DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
    }
}

4.创建数据源上下文处理器DynamicDataSourceContextHolder

创建数据源上下文处理器DynamicDataSourceContextHolder用以存储当前线程需要使用的数据源名称。

public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
        /**
         * 将 master 数据源的 key作为默认数据源的 key
         */
        @Override
        protected String initialValue() {
            return "master";
        }
    };


    /**
     * 数据源的 key集合,用于切换时判断数据源是否存在
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();

    /**
     * 切换数据源
     *
     * @param key
     */
    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }

    /**
     * 获取数据源
     *
     * @return
     */
    public static String getDataSourceKey() {
        return contextHolder.get();
    }

    /**
     * 重置数据源
     */
    public static void clearDataSourceKey() {
        contextHolder.remove();
    }

    /**
     * 判断是否包含数据源
     *
     * @param key 数据源key
     * @return
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }

    /**
     * 添加数据源keys
     *
     * @param keys
     * @return
     */
    public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
        return dataSourceKeys.addAll(keys);
    }
}

5.创建数据源配置类DataSourceConfig

创建数据源配置类DataSourceConfig,将所有数据源注入到spring容器

@Configuration
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class }) // 排除 DataSourceAutoConfiguration 的自动配置,避免环形调用
public class DataSourceConfig {

    @Autowired
    private MasterDataSourceConfig masterDataSourceConfig;

    @Autowired
    private DB1DataSourceConfig dB1DataSourceConfig;
    /**
     * 设置动态数据源为主数据源
     *
     * @return
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认指定的数据源
        dynamicDataSource.setDefaultDataSource(masterDataSourceConfig.masterDataSource());
        // 将数据源设置进map
        Map<Object, Object> dataSourceMap = new HashMap<>(8);
        dataSourceMap.put(DataSourceEnum.MASTER.toString(), masterDataSourceConfig.masterDataSource());
        dataSourceMap.put(DataSourceEnum.DB1.toString(), dB1DataSourceConfig.db1DataSource());
        // 使用 Map 保存多个数据源,并设置到动态数据源对象中,这个值最终会在afterPropertiesSet中被设置到resolvedDataSources上
        dynamicDataSource.setDataSources(dataSourceMap);
        return dynamicDataSource;
    }
}

6.创建数据源类型枚举DataSourceEnum

public enum DataSourceEnum {

    /**默认类型*/
    MASTER,
    /**DB1类型*/
    DB1,
    ;
}

7.创建自定义注解@DataSource

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

    /**
     * 数据源key值
     * @return
     */
    DataSourceEnum value();
}

8.创建切面DynamicDataSourceAspect

@Slf4j
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {

    /**
     * 切换数据源
     *
     * @param point
     * @param dataSource
     */
    @Before("@annotation(dataSource))")
    public void switchDataSource(JoinPoint point, DataSource dataSource) {
        if (!DynamicDataSourceContextHolder.containDataSourceKey(dataSource.value().toString())) {
            log.info("DataSource [{}] doesn't exist, use default DataSource", dataSource.value());
        } else {
            // 切换数据源
            DynamicDataSourceContextHolder.setDataSourceKey(dataSource.value().toString());
            log.info("Switch DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(),
                    point.getSignature());
        }
    }

    /**
     * 重置数据源
     *
     * @param point
     * @param dataSource
     */
    @After("@annotation(dataSource))")
    public void restoreDataSource(JoinPoint point, DataSource dataSource) {
        // 将数据源置为默认数据源
        DynamicDataSourceContextHolder.clearDataSourceKey();
        log.info("Restore DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
    }
}

如何使用

@Override
@DataSource(DataSourceEnum.DB1)
public Page<AuditTaskDto> queryAuditTask(AuditTaskQuery query) {
    Page<AuditTaskDto> page = baseMapper.queryAuditTask(query);
    return page;
}

标签:实战,return,SpringBoot,数据源,DataSource,key,public,datasource
From: https://www.cnblogs.com/GilbertDu/p/18384951

相关文章

  • SpringBoot配置多个kafka配置
    引入依赖<dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId><version>2.7.14</version></dependency>yml配置有几个就配置几个......
  • 解决云服务器被攻击至黑洞状态的实战指南
    当云服务器遭遇大规模的DDoS攻击时,为了保护网络基础设施和其他客户的服务不受影响,云服务提供商通常会将受到攻击的服务器置于所谓的“黑洞”状态——即完全屏蔽其对外的所有网络连接。本文将详细介绍云服务器被攻击至黑洞状态的原因、识别方法以及解决策略。黑洞状态简介......
  • 【整理】 【Windows系列】Windows安全日志分析实战:关键事件+详解
    参考......
  • Springboot计算机毕业设计汽车销售管理系统3ytu1
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表用户,员工,汽车分类,汽车品牌,汽车颜色,汽车信息,汽车预订,汽车入库,数据统计,反馈信息开题报告内容一、毕业设计(论文)题目的来源、理论或实际应用意义1.1题目......
  • 基于java的SpringBoot框架卫生健康系统
    博主介绍:java高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、爬虫、web开发,已经做了六年的程序开发,开发过上千套大学生实战程序,可以定制、也可成品项目,博客中有上百套程序可供参考,欢迎共同交流学习。......
  • 使用idea快速创建springbootWeb项目(springboot+springWeb+mybatis-Plus)
    idea快速创建springbootWeb项目详细步骤如下1)创建项目2)选择springboot版本3)添加web依赖4)添加Thymeleaf5)添加lombok依赖然后点击create进入下一步双击pom.xml文件6)添加mybatis-plus依赖        这里使用的springboot版本比较新,mybatis-plus-boot-star......
  • OpenTelemetry 实战:从零实现应用指标监控
    前言在上一篇文章:OpenTelemetry实战:从零实现分布式链路追踪讲解了链路相关的实战,本次我们继续跟进如何使用OpenTelemetry集成metrics监控。建议对指标监控不太熟的朋友可以先查看这篇前菜文章:从Prometheus到OpenTelemetry:指标监控的演进与实践名称作用语言版本......
  • 记某项目的二顾茅庐5K实战
    一顾茅庐漏洞一:存在逻辑缺陷导致无限发布新动态和可修改动态问题可以看到此时发布了一个动态,还可以发布两个动态。点击发布新动态,填写好信息点击提交并抓包可以发现成功发布,回到动态页面可以看到可发布次数还是2,并且新发布的动态比正常发布的还多了一个修改的功能,可以正常......
  • Kubernetes (K8s) 监控方案:Prometheus 实战指南
    1.引言在当今云原生时代,Kubernetes(K8s)已成为容器编排的标准解决方案。然而,随着K8s集群规模和复杂性的增加,有效的监控变得至关重要。本文将详细介绍如何使用Prometheus构建一个全面而强大的K8s监控系统,帮助您实时掌握集群状态,快速定位问题,并优化资源利用。2.监......
  • springboot+vue+mybatis计算机毕业设计云养鸡互动平台+PPT+论文+讲解+售后
    快速发展的社会中,人们的生活水平都在提高,生活节奏也在逐渐加快。为了节省时间和提高工作效率,越来越多的人选择利用互联网进行线上打理各种事务,然后线上管理系统也就相继涌现。与此同时,人们开始接受方便的生活方式。他们不仅希望页面简单大方,还希望操作方便,可以快速锁定他们需......