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

前言

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

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

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

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

# 数据源配置
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.cj.jdbc.Driverdruid:# 主库数据源master:url: jdbc:mysql://xxx.xxx.xxx.101:3306/test1?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8username: rootpassword: root123# 从库数据源slave:# 从数据源开关/默认关闭enabled: trueurl: jdbc:mysql://xxx.xxx.xxx.102:3306/test2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8username: root123password: root123oracle:enabled: trueurl: jdbc:oracle:thin:@//xxx.xxx.xxx.103:1521/test3?useUnicode=true&characterEncoding=AL32UTF8username: rootpassword: 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")@Primarypublic 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(); // 初始化}@Overrideprotected 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.Driverurl: jdbc:mysql://localhost:3306/db1username: userpassword: passwordsecondary:  # 第二个数据源driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/db2username: userpassword: 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.mzph.cn/web/8029.shtml

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

相关文章

[SWPUCTF 2021 新生赛]PseudoProtocols、[SWPUCTF 2022 新生赛]ez_ez_php

[SWPUCTF 2021 新生赛]PseudoProtocols 打开环境&#xff0c;提示hint.php就在这里&#xff0c;且含有参数wllm 尝试利用PHP伪协议读取该文件 ?wllmphp://filter/convert.base64-encode/resourcehint.php//文件路径php://filter 读取源代码并进行base64编码输出。 有一些敏…

scikit-learn实现单因子线性回归模型

1.是什么&#xff1a; 针对机器学习提供了数据预处理&#xff0c;分类&#xff0c;回归等常见算法的框架 2.基于scikit-learn求解线性回归的问题&#xff1a; 2.1.求解a&#xff0c;b对新数据进行预测&#xff1a; 2.2评估模型表现&#xff08;y和y’的方差MSE&#xff09;…

Python轴承故障诊断 (18)基于CNN-TCN-Attention的创新诊断模型

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Python轴承故障诊断 (一)短时傅里叶变换STFT Python轴承故障诊断 (二)连续小波变换CWT_pyts 小波变换 故障-CSDN博客 Python轴承故障诊断 (三)经验模态分解EMD_轴承诊断 …

【Linux系列】file命令

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

hadoop学习---基于Hive的数据仓库相关函数机制及其优化方案

Hive相关函数&#xff08;部分&#xff09;&#xff1a; if函数: 作用: 用于进行逻辑判断操作 语法: if(条件, true返回信息,false返回信息) 注意: if函数支持嵌套使用 select if(aa,’bbbb’,111) fromlxw_dual; bbbb select if(1<2,100,200) fromlxw_dual; 200nvl函数:…

25_Scala集合Tuple

文章目录 tuple1.元组定义2.Tuple元素访问3.如果元素的len2&#xff0c;称之为键值对对象&#xff0c;也称之为对偶元组4.补充上节Map5.Map集合遍历6.集合之间相互转化 tuple 概念&#xff1a;scala语言采用特殊的方式将无关的数据作为一个整体&#xff0c;组合在一起’ 1.元…

2024爆火的AI设备Rabbit R1到底是什么?有人说它是AI的iPhone时刻,有人说它是套壳的安卓

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

c++编程(10)——string

欢迎来到博主的专栏——c编程 博主ID&#xff1a;代码小豪 文章目录 <string>string类的接口构造、析构、与赋值重载构造函数赋值重载运算符 元素访问operator[] 容量修改器对string对象的操作迭代器 std::string是定义在c标准的一个类&#xff0c;定义在标准库<strin…

Unity 性能优化之图片优化(八)

提示&#xff1a;仅供参考&#xff0c;有误之处&#xff0c;麻烦大佬指出&#xff0c;不胜感激&#xff01; 文章目录 前言一、可以提前和美术商量的事1.避免内存浪费&#xff08;UI图片&#xff0c;不是贴图&#xff09;2.提升图片性能 二、图片优化1.图片Max Size修改&#x…

Momentum靶机系列Momentum2

先进行arp扫描&#xff1a; 获得渗透靶机的IP&#xff1a;192.168.13.142 扫描一下靶机的使用的端口&#xff1a; 具有tcp端口和http服务的80端口 可以扫描一下80端口的http服务&#xff1a; 可以发现一个网站&#xff1a;http://192.168.13.142 打开该网址&#xff1a; 查看…

Flink 部署模式

目录 概述 部署模式 会话模式&#xff08;Session Mode&#xff09; 单作业模式(Per-Job Mode) 应用模式(Application Mode) 运行模式&#xff08;资源管理模式&#xff09; Standalone运行模式 会话模式部署 应用模式部署 Yarn运行模式 会话模式部署 单作业模式部…

鸿蒙开发核心技术都有哪些【都是从零开始】

鸿蒙开发核心技术都有哪些&#xff1f;&#xff1a;【持续1年的时间公关鸿蒙技术】 我们能做哪些呢&#xff1f; 还是从UI业务开始吧 面试题1&#xff1a; 基于STAGE模型项目重构等问题 代理设计模式&#xff0c;业务与架构隔离 中介者模式&#xff0c;和代理设计模式的区别…

湖仓一体 - Apache Arrow的那些事

湖仓一体 - Apache Arrow的那些事 Arrow是高性能列式内存格式标准。它的优势&#xff1a;高效计算&#xff1a;所有列存的通用优势&#xff0c;CPU缓存友好、SIMD向量化计算友好等&#xff1b;零序列化/反序列化&#xff1a;arrow的任何数据结构都是一段连续的内存&#xff0c;…

uniapp使用iconfont

1、把这两个文件在项目的静态资源目录下 2、修改iconfont.css文件 3、最后在app.vue中引入

AI换脸免费软件Rope中文汉化蓝宝石版本全新UI界面,修复部分已知错误【附下载地址与详细使用教程】

rope蓝宝石版&#xff1a;点击下载 注意&#xff1a;此版本支持N卡、A卡、CPU&#xff0c;且建议使用中高端显卡&#xff0c;系统要求win10及以上。 Rope-蓝宝石 更新内容&#xff1a; 0214版更新&#xff1a; ①&#xff08;已修复&#xff09;恢复到以前的模型荷载参数。有…

Python中GDAL批量将多个遥感影像各波段数值缩小10000倍的方法

本文介绍基于Python中的gdal模块&#xff0c;批量读取大量多波段遥感影像文件&#xff0c;分别对各波段数据加以数值处理&#xff0c;并将所得处理后数据保存为新的遥感影像文件的方法。 首先&#xff0c;看一下本文的具体需求。我们现有一个文件夹&#xff0c;其中含有大量.ti…

OceanBase 如何实现多层面的资源隔离

OceanBase的资源隔离涵盖了多个方面&#xff0c;如物理机器间的隔离、不同租户之间的隔离、同一租户内的隔离&#xff0c;以及针对大型查询请求的隔离等。在实际应用OceanBase的过程中&#xff0c;我们经常会遇到这些操作场景或产生相关需求。这篇文章针对这些内容进行了简要的…

软件测试--接口测试

接口测试&#xff1a;直接对后端服务的测试&#xff0c;是服务端性能测试的基础 接口&#xff1a;系统之间数据交互的通道 接口测试&#xff1a;校验接口响应数据与预期数据是否一致

docker私有仓库部署与管理

一、搭建本地公有仓库 1.1 首先下载registry镜像 docker pull registry 1.2 在daemon.json文件中添加私有镜像仓库地址并重新启动docker服务 vim /etc/docker/daemon.json 1.3 运行registry容器 docker run -itd -v /data/registry:/var/lib/registry -p 5000:5000 --restartal…

04_SpringCloud

文章目录 单体架构与微服务架构的介绍单体架构微服务架构 微服务的实现服务之间的调用服务注册中心Eureka 注册中心Eureka的自我保护机制Nacos注册中心 单体架构与微服务架构的介绍 单体架构 单体架构 所有的代码最终打包成一个文件(jar包)&#xff0c;整个系统的所有功能单元…