SpringBoot @DS注解 和 DynamicDataSource自定义实现多数据源的2种实现方式

news2024/11/17 8:46:29

前言

在实际的项目中,我们经常会遇到需要操作多个数据源的情况,SpringBoot为我们提供了多种实现多数据源的方式。本文将介绍两种常见的方式:使用@DS注解实现多数据源的切换以及使用DynamicDataSource自定义实现多数据源的切换。

我们将分别介绍这两种方法的实现原理和代码实现,并对比它们的优劣势。

方式一、使用DynamicDataSource实现多数据源

1、在application.yml文件中配置多个数据源

# 数据源配置
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        druid:
            # 主库数据源
            master:
                url: jdbc:mysql://xxx.xxx.xxx.101:3306/test1?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
                username: root
                password: root123
            # 从库数据源
            slave:
              # 从数据源开关/默认关闭
                enabled: true
                url: jdbc:mysql://xxx.xxx.xxx.102:3306/test2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
                username: root123
                password: root123
            oracle:
              enabled: true
              url: jdbc:oracle:thin:@//xxx.xxx.xxx.103:1521/test3?useUnicode=true&characterEncoding=AL32UTF8
              username: root
              password: root123

2、添加数据源到targetDataSources集合中

创建配置文件,把多个数据源添加到targetDataSources集合中,然后返回动态数据源配置。

/**
 * druid 配置多数据源
 * 
 * @author admin
 */
@Configuration
public class DruidConfig
{

    /**
     * master数据源的配置
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties)
    {
        // 创建Druid数据源
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        // 返回由Druid属性配置后的数据源
        return druidProperties.dataSource(dataSource);
    }

    /**
     * slave数据源的配置
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties)
    {
        // 创建Druid数据源
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        // 返回由Druid属性配置后的数据源
        return druidProperties.dataSource(dataSource);
    }

    /**
     * oracle数据源的配置
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.oracle")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.oracle", name = "enabled", havingValue = "true")
    public DataSource oracleDataSource(DruidProperties druidProperties)
    {
        // 创建Druid数据源
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        // 设置Oracle数据库驱动类
        dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
        // 返回由Druid属性配置后的数据源
        return druidProperties.dataSource(dataSource);
    }

    /**
     * 动态数据源配置
     */
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource)
    {
        // 目标数据源的映射
        Map<Object, Object> targetDataSources = new HashMap<>();
        // 将master数据源放入映射中
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        // 将slave数据源放入映射中
        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
        // 将oracle数据源放入映射中
        setDataSource(targetDataSources, DataSourceType.ORACLE.name(), "oracleDataSource");
        // 返回动态数据源对象
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }


    /**
     * 设置数据源
     * 
     * @param targetDataSources 备选数据源集合
     * @param sourceName 数据源名称
     * @param beanName bean名称
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        try
        {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        }
        catch (Exception e)
        {
        }
    }
}

上述代码中DynamicDataSource继承了AbstractRoutingDataSource抽象类,AbstractRoutingDataSource抽象类是动态数据源的核心实现类,它包含了几个关键方法:

  • determineCurrentLookupKey():这是一个抽象方法,必须由具体的子类实现。它的作用是决定当前应该使用哪个数据源的标识。当应用程序需要访问数据库时,AbstractRoutingDataSource会调用这个方法来确定要使用的数据源。
  • setTargetDataSources(Map<Object, Object> targetDataSources):这个方法用于设置目标数据源的Map。Map中的键值对表示数据源的标识和对应的数据源实例。当AbstractRoutingDataSource需要根据标识选择数据源时,会根据这个Map来进行查找。
  • setDefaultTargetDataSource(Object defaultTargetDataSource):这个方法用于设置默认的数据源。当在无法确定要使用的数据源时,AbstractRoutingDataSource会使用默认的数据源。
  • afterPropertiesSet():这个方法用于在设置完属性后进行一些必要的初始化工作。例如,在设置完目标数据源和默认数据源后,需要调用这个方法来确保AbstractRoutingDataSource的正确初始化。

通过show dragrams我们可以看到AbstractRoutingDataSource抽象类的关系图:

再看DynamicDataSource的具体实现:

public class DynamicDataSource extends AbstractRoutingDataSource
{
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
    {
        // 调用父类构造函数设置默认数据源和目标数据源
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet(); // 初始化
    }

    @Override
    protected Object determineCurrentLookupKey()
    {
        // 获取当前数据源的标识符
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

它主要做的事情就是调用父类构造函数设置默认数据源和目标数据源,完成初始化,然后重写determineCurrentLookupKey() 方法,返回当前数据源的标识。

3、动态数据源的使用

在实际开发过程中,我们通常使用开发注解的方式完成多数据源的切换,现在我们创建一个注解@DataSource,使用注解:

    @DataSource(value = DataSourceType.ORACLE)
    public Object test()
    {
        return userMapper.selectUserList();
    }

注解核心实现:

    @Around("dataPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable
    {
        DataSource dataSource = getDataSource(point);
        // 获取数据源的名称
        if (StringUtils.isNotNull(dataSource)) {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return point.proceed();
        }
        finally {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

DynamicDataSourceContextHolder类实现,DynamicDataSourceContextHolder类使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本。然后根据set赋值获取自己当前所需的数据源,而不会影响其它线程所对应的副本。

具体实现如下:

public class DynamicDataSourceContextHolder {
    
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
    
    /**
     *  使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> THREAD_LOCAL_DATA = new ThreadLocal<>();

    /**
     * 设置数据源
     */
    public static void setDataSourceType(String dsType) {
        THREAD_LOCAL_DATA.set(dsType);
    }

    /**
     * 获得数据源
     */
    public static String getDataSourceType() {
        return THREAD_LOCAL_DATA.get();
    }

    /**
     * 清空数据源
     */
    public static void clearDataSourceType()
    {
        THREAD_LOCAL_DATA.remove();
    }
}

4、动态数据源实现流程总结:

首先,在Spring Boot应用的配置文件中配置多个数据源,例如MySQL和Oracle数据源,并创建一个自定义的DynamicDataSource类,继承AbstractRoutingDataSource。

在DynamicDataSource类中,需要重写determineCurrentLookupKey方法,该方法根据当前线程上下文中保存的数据源类型来确定当前应该使用的数据源。

接着,可以通过AOP拦截器或其他方式,在每次数据库操作之前根据业务逻辑设置当前线程的数据源类型到ThreadLocal中。这样在调用数据库操作时,DynamicDataSource会根据ThreadLocal中保存的数据源类型来选择对应的数据源进行操作。

最后,通过自定义注解@DataSource(value = DataSourceType.ORACLE)来标记需要切换数据源的方法或类,利用AOP切面编程,在方法执行前根据注解值设置当前线程的数据源类型,从而实现动态数据源切换的功能。

方式二、使用@DS注解实现多数据源

1、添加pom依赖:

注解@DS是基于dynamic-datasource-spring-boot-starter 实现多数据源切换的,添加pom依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

2、配置数据源:

在application.properties或application.yml文件中配置多个数据源的连接信息。

spring:
  datasource:
    dynamic:
      primary:  # 主数据源
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/db1
        username: user
        password: password
      secondary:  # 第二个数据源
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/db2
        username: user
        password: password

3、使用数据源:

在服务类或数据访问层中通过@DS注解来指定使用哪个数据源,例如:

@DS("primary")
public List<User> listUsersFromPrimaryDataSource() {
    return userDao.listUsers();
}

@DS("secondary")
public List<User> listUsersFromSecondaryDataSource() {
    return userDao.listUsers();
}

4、实现原理:

从DynamicDataSourceAutoConfiguration作为入口,我们可以看@DS注解的实现原理

其实通过源码跟踪,不难发现@DS实现数据源切换,也是根据 AOP 切面、注解、ThreadLocal 等方式实现。@DS注解的封装,可以在方法或类上直接指定数据源,而无需修改代码。这使得切换数据源变得更加简单和直接。

通过源码查看:

其实,通过上述源码查看,可大致概括@DS注解的实现原理,主要如下:

扫描数据源配置信息: 在启动时,DynamicDataSourceAutoConfiguration 会扫描项目中定义的数据源配置信息,例如在配置文件中定义的多个数据源的连接信息。

创建数据源对象: 根据扫描到的数据源配置信息,动态创建对应的数据源对象,可以是基于不同数据库的 DataSource 实现类,如 DruidDataSource、HikariDataSource 等。

数据源路由策略: 定义数据源的路由策略,即根据业务需求或者特定条件来决定使用哪个数据源。这可以通过 AOP 切面、注解、ThreadLocal 等方式实现。

数据源切换: 在需要访问数据库的地方,根据路由策略选择合适的数据源,并将该数据源设置为当前线程的数据源上下文中,以确保后续的数据库操作都使用选定的数据源。

数据源清理: 在数据源使用完毕后,需要及时清理数据源上下文,避免数据源泄漏或混乱。

总的来说,DynamicDataSourceAutoConfiguration 的实现原理主要涉及数据源的创建、路由策略的制定和数据源的切换管理。通过这些步骤,可以实现在运行时动态切换数据源,从而实现多数据源的灵活应用。

以上就是SpringBoot @DS注解 和 DynamicDataSource自定义实现多数据源的实现方式,可根据实际业务需要进行选择和调整。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1664648.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

土地档案管理关系参考论文(论文 + 源码)

【免费】javaEE土地档案管理系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89296786 土地档案管理关系 摘 要 研究土地档案管理关系即为实现一个土地档案管理系统。土地档案管理系统是将现有的历史纸质档案资料进行数字化加工处理&#xff0c;建成标准化的…

boost asio同步编程(附源码api)

首先注明&#xff0c;这里我写的都是关于tcp的通信。 通信大致流程 创建端点 创建tcp端点的api是boost::asio::ip::tcp::endpoint; 当然创建udp端点的api则是boost::asio::ip::udp::endpoint; 是一个表示 TCP/UDP 端点的类&#xff0c;在 Boost.Asio 库中用于网络编程。它通…

工业机器人应用实践之玻璃涂胶(篇三)

工业机器人 接上篇文章&#xff0c;浅谈一下实践应用&#xff0c;具体以玻璃涂胶为例&#xff1a; 了解工业机器人在玻璃涂胶领域的应用 认识工具坐标系的标定方法 掌握计时指令的应用 掌握人机交互指令的应用 掌握等待类指令用法&#xff08;WaitDI、WaitUnitl 等&#xff0…

正点原子Linux学习笔记(九)在 LCD 上显示字符

在 LCD 上显示字符 23.1 原始方式&#xff1a;取模显示字符23.2 freetype 简介23.3 freetype 移植下载 FreeType 源码交叉编译 FreeType 源码安装目录下的文件移植到开发板 23.4 freetype 库的使用初始化 FreeType 库加载 face 对象设置字体大小加载字形图像 23.5 示例代码 前面…

再谈毕业论文设计投机取巧之IVR自动语音服务系统设计(信息与通信工程A+其实不难)

目录 举个IVR例子格局打开&#xff0c;万物皆能IVR IVR系统其实可盐可甜。还能可圈可点。 戎马一生&#xff0c;归来依然IVR。 举个IVR例子 以下是IVR系统的一个例子。 当您拨打电话进入IVR系统。 首先检验是否为工作时间。 如是&#xff0c;您将被送入ivr-lang阶段&#xff0…

【Delphi 爬虫库 6】使用正则表达式提取猫眼电影排行榜top100

正则表达式库的简单介绍 正则表达式易于使用&#xff0c;功能强大&#xff0c;可用于复杂的搜索和替换以及基于模板的文本检查。这对于输入形式的用户输入验证特别有用-验证电子邮件地址等。您还可以从网页或文档中提取电话号码&#xff0c;邮政编码等&#xff0c;在日志文件中…

机器学习算法应用——CART决策树

CART决策树&#xff08;4-2&#xff09; CART&#xff08;Classification and Regression Trees&#xff09;决策树是一种常用的机器学习算法&#xff0c;它既可以用于分类问题&#xff0c;也可以用于回归问题。CART决策树的主要原理是通过递归地将数据集划分为两个子集来构建决…

在 Kubernetes 上运行 Apache Spark 进行大规模数据处理的实践

在刚刚结束的 Kubernetes Community Day 上海站&#xff0c;亚马逊云科技在云原生分论坛分享的“在 Kunernets 上运行 Apache Spark 进行大规模数据处理实践”引起了现场参与者的关注。开发者告诉我们&#xff0c;为了充分利用 Kubernetes 的高可用设计、弹性&#xff0c;在越来…

Leedcode题目:移除链表元素

题目&#xff1a; 这个题目就是要我们将我们的链表中的值是val的节点删除。 我们题目提供的接口是 传入了指向一个链表的第一个节点的指针&#xff0c;和我们要删除的元素的值val&#xff0c;不只要删除第一个&#xff0c; 思路 我们这里可以创建一个新的链表&#xff0c;…

配置Docker对象与管理守护进程

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 本章节的快速目录导航&#xff1a; 一、配置Docker对象 1.1、Docker对象的标记 1.2、格式化命令和日志的输出 二、示例&#xff1a; 2.1、管理…

HTML【常用的标签】、CSS【选择器】

day45 HTML 继day44&#xff0c;w3cschool 常用的标签 k) 表格 表格由 table 标签来定义。每个表格均有若干行&#xff08;由 tr 标签定义&#xff09;&#xff0c;每行被分割为若干单元格&#xff08;由 标签定义&#xff09;。字母 td指表格数据&#xff08;table data&…

Qt | QSpinBox 类 QDoubleSpinBox 类(微调框)

01、QSpinBox 类 1、QSpinBox类是 QAbstractSpinBox 类的直接子类和具体实现, 2、QSpinBox 类被设计用于处理整数和离散值集合,对于浮点值使用 QDoubleSpinBox 类实现。 3、QSpinBox 默认只支持整数值,但可通过其内部的成员函数进行扩展,以支持使用不同的 字符串。 02…

ppt通过修改幻灯片母版修改页脚

修改幻灯片母版 幻灯片母版就可以了&#xff0c;就可以修改页脚

c++多态机制

多态 在 C 中&#xff0c;多态&#xff08;Polymorphism&#xff09;是一种面向对象编程的重要概念&#xff0c;它允许不同类的对象对同一消息做出不同的响应。具体来说&#xff0c;多态性允许基类的指针或引用在运行时指向派生类的对象&#xff0c;并且根据对象的实际类型来调…

汇昌联信科技:做拼多多网店要押金吗?

做拼多多网店要押金吗?”这个问题&#xff0c;其实与拼多多的平台规则有关。在开店之前&#xff0c;商家需要详细了解平台的各项规定和费用构成&#xff0c;这样才能做好充足的准备。 一、明确回答问题 做拼多多网店&#xff0c;不需要支付押金。拼多多的入驻门槛相对较低&…

烽火三十六技丨网络资产安全治理平台新版本发布,一文看懂四大核心优势

云计算、移动互联网、物联网等技术飞速发展&#xff0c;网络环境愈发开放互联&#xff0c;原有的资产管理方式已难以适应当下的变化。同时&#xff0c;网络资产需求的突发性和人为疏忽&#xff0c;也时常导致资产数量不明、类型模糊、安全漏洞检查不全面等问题。因此&#xff0…

增加表空间的数据文件

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 表空间创建完成后&#xff0c;后期还可以为表空间增加数据文件&#xff0c;以扩大数据的存储空间。增加表空间数据文件的基本语法结构如下所示。 ALTER TABLESPACE 表空间名…

SpringCloud:服务拆分和远程调用

程序员老茶 &#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; P   S : 点赞是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#…

详解drop,delete,truncate区别

在SQL中&#xff0c;"DROP"、"DELETE"和"TRUNCATE"是用于删除数据的不同命令&#xff0c;它们之间有一些重要的区别&#xff1a; DROP&#xff1a; DROP用于删除数据库对象&#xff0c;例如删除表、视图、索引、触发器等。使用DROP删除的对象将…

如何撰写一份优秀的产品需求文档?看完真的不用加班了!

产品需求文档&#xff08;PRD&#xff09;是产品经理最主要的可视化交付物&#xff0c;刚入门的产品会疑惑产品需求文档应该如何撰写&#xff0c;产品老鸟们也会头痛产品文档如何高效更新和维护。本文会详细介绍2024年最新的极简产品文档撰写和管理方案&#xff0c;建议阅读并收…