springboot 配置多数据源以及动态切换数据源

ops/2025/1/22 16:41:49/

场景

我们springboot项目,通常会有多个数据库,例如mysql,vertica,postgresql等等数据库,通常我们需要动态切换使用我们想要的数据库,这时候就需要配置多数据源了

多数据源特性

支持多数据库类型:例如,同时连接 MySQL、PostgreSQL 和 Vertica 等不同数据库,在应用中能够根据不同的业务需求切换到对应的数据库

动态数据源切换:根据请求、业务逻辑或用户上下文,动态切换数据源(例如,按用户请求切换到不同的数据库),而不需要重启应用。

数据库隔离:不同业务模块使用不同的数据源,确保它们的数据完全隔离。例如,一个模块使用 MySQL,另一个模块使用 Vertica。

高可用和容错:通过配置多个数据源,可以在主数据库发生故障时自动切换到备用数据库,提升系统的高可用性。

实现

基于springboot3.4配置

引入依赖的方式

添加依赖
        <!--动态数据源--><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>4.3.0</version></dependency>
配置YML文件
spring:datasource:dynamic:# 设置默认的数据源或者数据源组,默认值即为 masterprimary: masterdatasource:master:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=falseusername: rootpassword: 123456slave:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/eshopping?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=falseusername: rootpassword: 123456
调用数据库

直接再类上加上@DS(“master”)注解即是连接的master数据库

java">@Service
@DS("master")
public class LoginServiceImpl implements LoginService{
问题

博主这里遇见了问题,因为我配置了JWT+SpringSecurity,这里报错了,信息如下,找不到数据源,猜想应该是和JwtAuthenticationFilter和数据源的加载顺序有关

java">2025-01-21 21:43:37.097 ERROR --- [           main] o.s.b.web.embedded.tomcat.TomcatStarter  : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'jwtAuthenticationFilter': Unsatisfied dependency expressed through field 'userDetailsService': Error creating bean with name 'customUserDetailsService': Unsatisfied dependency expressed through field 'loginMapper': Error creating bean with name 'loginMapper' defined in file [E:\IDE\demoNew\target\classes\com\example\demonew\mapper\loginMapper\LoginMapper.class]: Cannot resolve reference to bean 'sqlSessionTemplate' while setting bean property 'sqlSessionTemplate'
2025-01-21 21:43:37.134 INFO  --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2025-01-21 21:43:37.155 WARN  --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server
2025-01-21 21:43:37.167 INFO  --- [           main] .s.b.a.l.ConditionEvaluationReportLogger : Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-01-21 21:43:37.210 ERROR --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : ***************************
APPLICATION FAILED TO START
***************************Description:Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.Reason: Failed to determine a suitable driver classAction:Consider the following:If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.If you have database settings to be loaded from a particular profile you may need to activate it (the profiles dev are currently active).

手动实现

依赖默认已经引入,就是基础的mysql,mybatis依赖

配置YML文件
spring:datasource:master:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=falseusername: rootpassword: 123456slave:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/eshopping?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=falseusername: rootpassword: 123456
配置DataSourceContextHolder

创建DataSourceContextHolder 类通过ThreadLocal存储数据源信息

java">/*DataSourceContextHolder 类用于保存当前线程的数据库标识,通常使用 ThreadLocal 来保存当前的数据源。*/
public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setDataSourceType(String dataSourceType) {contextHolder.set(dataSourceType);}public static String getDataSourceType() {return contextHolder.get();}public static void clearDataSourceType() {contextHolder.remove();}
}
创建DynamicDataSource

创建DynamicDataSource 类继承AbstractRoutingDataSource ,可获取当前线程数据源信息

java">/*DynamicDataSource 类继承自 AbstractRoutingDataSource,用于根据当前线程的上下文动态选择数据源。*/
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {// 获取当前线程中的数据源标识return DataSourceContextHolder.getDataSourceType();}
}
配置DataSourceConfig

配置DataSourceConfig类,将配置文件的中的数据源配置成bean,然后存入DataSourceContextHolder 类中的ThreadLocal中存储

java">/*配置一个 DynamicDataSource,将多个 DataSource 放入其中,并设置默认数据源。*/
@Configuration
public class DataSourceConfig {@Primary@Bean(name = "masterDataSource")@ConfigurationProperties("spring.datasource.master")public HikariDataSource primaryDataSource() {return new HikariDataSource();}@Bean(name = "slaveDataSource")@ConfigurationProperties("spring.datasource.slave")public HikariDataSource secondaryDataSource() {return new HikariDataSource();}@Beanpublic DataSource dataSource() {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("masterDataSource", primaryDataSource());targetDataSources.put("slaveDataSource", secondaryDataSource());DynamicDataSource routingDataSource = new DynamicDataSource();routingDataSource.setDefaultTargetDataSource(primaryDataSource());routingDataSource.setTargetDataSources(targetDataSources);return routingDataSource;}}
使用

在serviceImpl中使用
主要是用下面的方法切换数据源DataSourceContextHolder.setDataSourceType(“masterDataSource”);

java">@Service
public class LoginServiceImpl implements LoginService{@Autowiredprivate LoginMapper loginMapper;@Overridepublic String register(String username, String password) {return "成功";}@Overridepublic String login(String username, String password) {DataSourceContextHolder.setDataSourceType("masterDataSource");String pw = loginMapper.login(username);if(password.equals(pw)){return "成功";}else {return "失败";}}}
清除

ThreadLocal不使用之后需要remove,不然容易造成数据污染或错误

原因

ThreadLocal 是与线程绑定的,在 Spring 或类似的框架中,线程池通常会重用线程,如果在请求处理过程中没有清理 ThreadLocal 中的内容,接下来复用的线程可能会带着之前请求中的 ThreadLocal 数据继续处理新的请求,从而导致数据污染或错误

假设你有两个数据源:primary 和 secondary,当请求 A 处理时,ThreadLocal 设置了 primary 数据源。
如果线程 A 继续处理请求 B,但此时没有清理 ThreadLocal,那么请求 B 可能会意外地使用 primary 数据源,而不是它本该使用的 secondary 数据源。

手动实现(注解版本)推荐

上面的版本每次都需要DataSourceContextHolder.setDataSourceType(“masterDataSource”);切换数据源非常麻烦,因此可以自定义注解

自定义注解

使用@DataSource时默认值masterDataSource

java">@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {String value() default "masterDataSource"; // 默认是 master 数据源
}
定义AOP切面

在使用该注解前将值赋值给DataSourceContextHolder中ThreadLocal存储,并可以方便的清除数据源

java">@Aspect
@Component
public class DataSourceAspect {@Before("@annotation(dataSource)")public void switchDataSource(DataSource dataSource) {String dataSourceType = dataSource.value();DataSourceContextHolder.setDataSourceType(dataSourceType);}@After("@annotation(dataSource)")public void clearDataSource(DataSource dataSource) {DataSourceContextHolder.clearDataSourceType();}@AfterThrowing("@annotation(dataSource)")public void handleException(DataSource dataSource) {DataSourceContextHolder.clearDataSourceType();}
}
使用
java">@Service
public class LoginServiceImpl implements LoginService{@Autowiredprivate LoginMapper loginMapper;@Overridepublic String register(String username, String password) {return "成功";}@Override@DataSource("slaveDataSource")public String login(String username, String password) {String pw = loginMapper.login(username);if(password.equals(pw)){return "成功";}else {return "失败";}}}

http://www.ppmy.cn/ops/152233.html

相关文章

IOC有什么优势

IOC&#xff08;控制反转&#xff0c;Inversion of Control&#xff09; 是一种设计原则&#xff0c;广泛应用于软件开发中&#xff0c;尤其是面向对象编程中。IOC 的主要优势体现在以下几个方面&#xff1a; 1. 解耦合&#xff08;Decoupling&#xff09; 减少依赖性&#x…

map和set的使用(一)详解

文章目录 序列式容器和关联式容器map和set的介绍set构造和迭代器遍历和insertfinderaseswapclearcountlower_bound和upper_boundmultiset和set的对比 set的二个题目题目解析算法原理代码介绍一个找差集的算法同步算法题目解析算法原理代码 map构造遍历initiaizer_list 序列式容…

【2024年CSDN平台总结:新生与成长之路】

&#x1f4ab;引言 2024年已经过去&#xff0c;回顾这一年&#xff0c;所有的经历依然历历在目。以“经验”为动力&#xff0c;我正迈向2025年。回顾自己在CSDN平台上的创作之路&#xff0c;收获满满、成长颇多&#xff0c;也有许多宝贵的感悟。接下来&#xff0c;我将分享这一…

[STM32 HAL库]串口中断编程思路

一、前言 最近在准备蓝桥杯比赛&#xff08;嵌入式赛道&#xff09;&#xff0c;研究了以下串口空闲中断DMA接收不定长的数据&#xff0c;感觉这个方法的接收效率很高&#xff0c;十分好用。方法配置都成功了&#xff0c;但是有一个点需要进行考虑&#xff0c;就是一般我们需要…

Java Web开发高级——单元测试与集成测试

测试是软件开发的重要环节&#xff0c;确保代码质量和功能的正确性。在Spring Boot项目中&#xff0c;单元测试和集成测试是常用的两种测试类型&#xff1a; 单元测试&#xff1a;测试单个模块&#xff08;如类或方法&#xff09;是否按预期工作。集成测试&#xff1a;测试多个…

StackOrQueueOJ3:用栈实现队列

目录 题目描述思路分析开辟队列入队列出队列 代码展示 题目描述 原题&#xff1a;232. 用栈实现队列 思路分析 有了前面的用队列实现栈的基础我们不难想到这题的基本思路&#xff0c;也就是用两个栈来实现队列&#xff0c;&#xff08;栈的实现具体参考&#xff1a;栈及其接口…

dl学习笔记:(7)完整神经网络流程

完整神经网络流程 反向传播链式求导 代码实现反向传播动量法Momentum开始迭代为什么选择小批量TensorDataset与DataLoader 反向传播 由于本节的公式比较多&#xff0c;所以如果哪里写错了漏写了&#xff0c;还请帮忙指出以便进行改正&#xff0c;谢谢。 在前面的章节已经介绍过…

C语言程序设计十大排序—冒泡排序

文章目录 1.概念✅2.冒泡排序&#x1f388;3.代码实现✅3.1 直接写✨3.2 函数✨ 4.总结✅ 1.概念✅ 排序是数据处理的基本操作之一&#xff0c;每次算法竞赛都很多题目用到排序。排序算法是计算机科学中基础且常用的算法&#xff0c;排序后的数据更易于处理和查找。在计算机发展…