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

前言

在实际的项目中,我们经常会遇到需要操作多个数据源的情况,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自定义实现多数据源的实现方式,可根据实际业务需要进行选择和调整。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Unity】在空物体上实现 IPointerClickHandler 不起作用

感谢Unity接口IPointerClickHandler使用说明_哔哩哔哩_bilibiliUnity接口IPointerClickHandler使用说明, 视频播放量 197、弹幕量 0、点赞数 3、投硬币枚数 2、收藏人数 2、转发人数 0, 视频作者 游戏创作大陆, 作者简介 &#xff0c;相关视频&#xff1a;在Unity多场景同时编辑…

扩散模型(Diffusion Model)概述

扩散模型&#xff08;Diffusion Model&#xff09;是图像生成模型的一种。有别于此前 AI 领域大名鼎鼎的 GAN、VAE 等算法&#xff0c;扩散模型另辟蹊径&#xff0c;其主要思想是一种先对图像增加噪声&#xff0c;再逐步去噪的过程&#xff0c;其中如何去噪还原图像是算法的核心…

移动机器人系统与技术:自动驾驶、移动机器人、旋翼无人机

这本书全面介绍了机器人车辆的技术。它介绍了道路上自动驾驶汽车所需的概念。此外&#xff0c;读者可以在六足机器人的构造、编程和控制方面获得宝贵的知识。 这本书还介绍了几种不同类型旋翼无人机的控制器和空气动力学。它包括各种旋翼推进飞行器在不同空气动力学环境下的模…

备考2024年小学生古诗文大会:吃透10道历年真题和知识点(持续)

根据往年的安排&#xff0c;2024年上海市小学生古诗文大会预计还有一个月就将启动。我们继续来随机看10道往年的上海小学生古诗文大会真题&#xff0c;这些题目来自我去重、合并后的1700在线题库&#xff0c;每道题我都提供了参考答案和独家解析。 根据往期的经验&#xff0c;只…

pg数据库学习知识要点分析-1

知识要点1 对象标识OID 在PostgreSQL内部&#xff0c;所有的数据库对象都通过相应的对象标识符&#xff08;object identifier&#xff0c;oid&#xff09;进行管理&#xff0c;这些标识符是无符号的4字节整型。数据库对象与相应oid 之间的关系存储在对应的系统目录中&#xf…

如何使用 Node.js 开发一个文件上传功能?

在 Node.js 中实现文件上传功能可以通过多种方式完成&#xff0c;但其中最常用的方法之一是使用 Express 框架和 Multer 中间件。Express 是一个流行的 Node.js Web 框架&#xff0c;而 Multer 是一个用于处理文件上传的中间件。 步骤 1: 准备工作 首先&#xff0c;确保你已经…

基于Springboot的旅游管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的旅游管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

数字旅游以科技创新为核心竞争力:推动旅游服务的智能化、高效化,满足游客日益增长的旅游需求

一、引言 随着科技的飞速发展&#xff0c;数字旅游作为旅游业与信息技术结合的产物&#xff0c;正以其独特的魅力改变着传统旅游业的格局。科技创新作为数字旅游的核心竞争力&#xff0c;不仅推动了旅游服务的智能化、高效化&#xff0c;更满足了游客日益增长的旅游需求。本文…

HIVE数据导出

HIVE数据导出 1.INSERT OVERWRITE LOCAL DIRECTORY "路径" SELECT 查询语句; INSERT OVERWRITE LOCAL DIRECTORY "/usr/local/soft/hive-3.1.2/data/output" select * from learn2.partition_student6; 导出数据时 通过执行MapReduce任务导出到本地文件系统…

Python安装win32api

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️感谢大家点赞&#x1f44d;&…

【云原生】Docker 实践(二):什么是 Docker 的镜像

【Docker 实践】系列共包含以下几篇文章&#xff1a; Docker 实践&#xff08;一&#xff09;&#xff1a;在 Docker 中部署第一个应用Docker 实践&#xff08;二&#xff09;&#xff1a;什么是 Docker 的镜像Docker 实践&#xff08;三&#xff09;&#xff1a;使用 Dockerf…

【neteq】tgcall的调用

G:\CDN\P2P-DEV\Libraries\tg_owt\src\call\call.cc基本是按照原生webrtc的来的:G:\CDN\P2P-DEV\tdesktop-offical\Telegram\ThirdParty\tgcalls\tgcalls\group\GroupInstanceCustomImpl.cpptg对neteq的使用 worker 线程创建call Call的config需要neteqfactory Call::CreateAu…

Git可视化工具tortoisegit 的下载与使用

一、tortoisegit 介绍 TortoiseGit 是一个非常实用的版本控制工具&#xff0c;主要用于与 Git 版本控制系统配合使用。 它的主要特点包括&#xff1a; 图形化界面&#xff1a;提供了直观、方便的操作界面&#xff0c;让用户更易于理解和管理版本控制。与 Windows 资源管理器…

MATLAB中自定义栅格数据地理坐标R,利用geotifwrite写入tif

场景描述&#xff1a; 有时候将nc格式的数据转成tiff&#xff0c;或者是将一个矩阵输出成带有地理坐标信息tiff数据时&#xff0c;常常涉及到空间参考的定义和geotiffwrite()函数。 问题描述&#xff1a; 以全球数据为例&#xff0c;今天发现在matlab中对矩阵进行显示后&…

android zygote进程启动流程

一&#xff0c;启动入口 app_main.cpp int main(int argc, char* const argv[]) {if (!LOG_NDEBUG) {String8 argv_String;for (int i 0; i < argc; i) {argv_String.append("\"");argv_String.append(argv[i]);argv_String.append("\" ")…

DiffSeg——基于Stable Diffusion的无监督零样本图像分割

概述 基于计算机视觉的模型的核心挑战之一是生成高质量的分割掩模。大规模监督训练的最新进展已经实现了跨各种图像风格的零样本分割。此外&#xff0c;无监督训练简化了分割&#xff0c;无需大量注释。尽管取得了这些进展&#xff0c;构建一个能够在没有注释的零样本设置中分…

ElementUI从unpkg.com完整下载到本地的方法 - 解决unpkg.com不稳定的问题 - 自建镜像站 - 不想打包只想cdn一下

方法 方法1&#xff09;随便弄个文件夹&#xff0c;根据官网npm方法下载包&#xff0c;提取即可 npm i element-ui -S cd /node_modules/element-ui/ ls src 安装npm方法&#xff1a;https://nodejs.org/en 方法2&#xff09;不推荐 - 在github中搜索对应的库zip包&#xff0…

视频编辑软件pitivi基本功之将三个相关视频合并成一个视频

视频编辑软件pitivi基本功之将三个相关视频合并成一个视频 一、素材来源&#xff1a;网站下载 到http://cpc.people.com.cn/GB/67481/435238/437822/437828/437900/index.html下载以下三个视频&#xff0c;鼠标右击视频——另存视频为 庆祝中国共产党成立100周年大会即将开始—…

用keras识别狗狗

一、需求场景 从照片从识别出狗狗 from keras.applications.resnet50 import ResNet50 from keras.preprocessing import image from keras.applications.resnet50 import preprocess_input, decode_predictions import numpy as np# 加载预训练的ResNet50模型 model ResNet5…

240503-关于Unity的二三事

240503-关于Unity的二三事 1 常用快捷键 快捷键描述CtrlP播放/停止Ctrl1打开Scene窗口Ctrl2打开Game窗口Ctrl3打开Inspect窗口Ctrl4打开Hierarchy窗口Ctrl5打开Project窗口Ctrl6打开Animation窗口 2 关联VisualStudio2022 3 节约时间&#xff1a;将最新声明的参数移动到最上…