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

server/2024/10/19 1:28:28/

前言

在实际的项目中,我们经常会遇到需要操作多个数据源的情况,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集合中,然后返回动态数据源配置。

java">/*** 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的具体实现:

java">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,使用注解:

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

注解核心实现:

java">    @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赋值获取自己当前所需的数据源,而不会影响其它线程所对应的副本。

具体实现如下:

java">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依赖:

java"><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注解来指定使用哪个数据源,例如:

java">@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.ppmy.cn/server/38284.html

相关文章

Java特性Function基础功能特性使用

在 Java 8 中&#xff0c;Function 接口是一个函数接口&#xff0c;它位于包 java.util.function 下。 Function 接口中定义了一个 R apply(T t) 方法&#xff0c;它可以接受一个泛型 T 对象&#xff0c;返回一个泛型 R 对象&#xff0c;即参数类型和返回类型可以不同。 Functi…

软件设计师-应用技术-面向对象程序设计题5

考题形式&#xff1a; 代码填空&#xff0c;5 - 6空&#xff0c;每空3分。 基础知识及技巧&#xff1a; 1. 类的定义&#xff1a; 2. 接口的定义&#xff1a; 给实现类具体代码&#xff0c;填写接口中方法。 3. 类、抽象类、继承类、抽象方法的定义&#xff1a; 抽象类&…

RESTful API 构建 Web 应用程序

RESTful API 是一种设计风格&#xff0c;用于创建和管理 Web 应用程序的 API。REST&#xff08;Representational State Transfer&#xff09;表示一组规范和约定&#xff0c;用于在网络上传输和操作数据。RESTful API 使用 HTTP 方法&#xff08;如 GET、POST、PUT、DELETE&am…

鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙防掉坑指南

几点说明 kernel_liteos_a_note | 中文注解鸿蒙内核 是在 OpenHarmony 的 kernel_liteos_a 基础上给内核源码加上中文注解的版本.与官方源码按月保持同步,同步历史如下: 2021/10/09 – 增加性能优化模块perf,优化了文件映射模块2021/09/14 – common,extended等几个目录结构和M…

鼠标移到图片上,光线闪过效果的实现

本文用两种方式实现了鼠标移动到图片上时&#xff0c;有光线闪过的效果。 第一种&#xff1a;使用::before伪元素实现。 1.html代码部分 <div classcontainer><div class"item"><a href"#"><div><img src"./img/dizi.j…

考研逆天改命,双非逆袭985!

这个系列会邀请上岸学长学姐进行经验分享~ 今天分享经验的同学是双非上岸中国海洋大学&#xff01; 大家好&#xff0c;先自我介绍一下&#xff0c;我是应届考生&#xff0c;报考的专业是新一代电子信息技术&#xff08;产教融合&#xff09;&#xff0c;总分377&#xff0c;…

C#高级编程笔记-运算符和类型强制转换

本章将首先讨论基本语言元素&#xff0c;接着论述C#语言的扩展功能。本章的主要内容如下&#xff1a; ● C#中的运算符 ● 使用nameof运算符和空值条件运算符 ● 隐式和显式转换 ● 使用装箱技术把值类型转换为引用类型 ● 比较值类型和引用…

JS笔试手撕题

数据劫持 Vue2的Object.defineProperty() Vue2的响应式是通过Object.defineProperty()拦截数据&#xff0c;将数据转换成getter/setter的形式&#xff0c;在访问数据的时候调用getter函数&#xff0c;在修改数据的时候调用setter函数。然后利用发布-订阅模式&#xff0c;在数…