使用 Spring Boot + AbstractRoutingDataSource 实现动态切换数据源

1. 动态切换数据源的原理

AbstractRoutingDataSource 是 Spring 提供的一个抽象类,它通过实现 determineCurrentLookupKey 方法,根据上下文信息决定当前使用的数据源。核心流程如下:

  • 定义多数据源配置:注册多个数据源。
  • 实现动态数据源路由:继承 AbstractRoutingDataSource,根据上下文返回数据源标识。
  • 使用拦截器设置上下文:在请求中设置当前使用的数据源。

 2. 实现步骤

2.1 确保你的 pom.xml 中已经包含如下依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>

 

2.2 继承自 Spring 提供的抽象类 AbstractRoutingDataSource
package com.imooc.cloud.springboot;import com.imooc.cloud.dynamic.raw.DataSourceContext;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import java.util.Map;public class SpringDynamicDataSource extends AbstractRoutingDataSource {public SpringDynamicDataSource(Map<Object, Object> targetDataSources) {super.setTargetDataSources(targetDataSources);}@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContext.getCurrentDb();}
}

类定义

public class SpringDynamicDataSource extends AbstractRoutingDataSource {
}
  • 继承自 Spring 提供的抽象类 AbstractRoutingDataSource
  • 是实现多数据源切换的核心类。

构造函数

public SpringDynamicDataSource(Map<Object, Object> targetDataSources) {super.setTargetDataSources(targetDataSources);
}
  • 通过构造器传入多个目标数据源(通常是 Map<标识符, DataSource> 形式)。
  • 调用父类方法设置这些数据源。

核心方法:determineCurrentLookupKey()

@Override
protected Object determineCurrentLookupKey() {return DataSourceContext.getCurrentDb();
}
  • Spring 框架会在每次数据库操作时调用这个方法。
  • 返回当前线程使用的数据源标识(如 "master""slave1")。
  • 实际上是从 ThreadLocal 中获取当前线程绑定的数据源名称。

2.3 数据源上下文工具类 
public class DataSourceContext {private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();public static void setCurrentDb(String db) {CONTEXT.set(db);}public static String getCurrentDb() {return CONTEXT.get();}public static void removeCurrentDb() {CONTEXT.remove();}
}

用于保存和清除当前线程使用的数据源标识。


2.4 将多数据源注入并创建 SpringDynamicDataSource
package com.imooc.cloud.springboot;import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;public class SpringDataSourceConfiguration {@Beanpublic DataSource mybatisPlusDataSource() {return DataSourceBuilder.create().driverClassName("com.mysql.jdbc.Driver").url("jdbc:mysql://192.168.3.150:3306/mybatisplus?characterEncoding=utf8").username("root").password("123456").build();}@Beanpublic DataSource mybatisExampleDataSource() {return DataSourceBuilder.create().driverClassName("com.mysql.jdbc.Driver").url("jdbc:mysql://192.168.3.150:3306/mybatis-example?characterEncoding=utf8").username("root").password("123456").build();}@Primary@Beanpublic SpringDynamicDataSource springDynamicDataSource() {Map<Object, Object> targetDataSources = new HashMap<>();DataSource mybatisPlusDataSource = mybatisPlusDataSource();DataSource mybatisExampleDataSource = mybatisExampleDataSource();targetDataSources.put("mybatisPlus", mybatisPlusDataSource);targetDataSources.put("mybatisExample", mybatisExampleDataSource);return new SpringDynamicDataSource(targetDataSources);}
}
2.5 安全地保存和切换当前线程使用的数据源

在多线程环境下,安全地保存和切换当前线程使用的数据源标识(如 "master""slave1" 等),支持嵌套调用(例如在事务中嵌套切换数据源),并且使用 双端队列(Deque)模拟栈结构 来管理数据源切换的上下文。 

  • 使用 ThreadLocal 保存每个线程独立的 数据源栈(Deque)
  • 使用 NamedThreadLocal 有助于在调试或日志中识别该线程局部变量的用途。
  • ArrayDeque 是一个双端队列,这里用作栈(LIFO),实现嵌套切换数据源的功能。
package com.imooc.cloud.util;import org.springframework.core.NamedThreadLocal;
import org.springframework.util.StringUtils;import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;public final class DynamicDataSourceContextHolder {/*** 双端队列其实本质就是一个栈*/private static final ThreadLocal<Deque<String>> DATASOURCE_CONTEXT = NamedThreadLocal.withInitial(() -> new ArrayDeque<>());private DynamicDataSourceContextHolder() {if (DATASOURCE_CONTEXT != null) {throw new RuntimeException("禁止反射创建");}}public static String getCurrentDataSource() {//todo 2023-07-31 修复补丁。因为可能返回null,而ConcurrentHashMap的get方法不能传入null,否则报空指针String peek = DATASOURCE_CONTEXT.get().peek();return Objects.isNull(peek) ? "" : peek;}public static String addDataSource(String dds) {String datasource = StringUtils.isEmpty(dds) ? "" : dds;DATASOURCE_CONTEXT.get().push(datasource);return datasource;}public static void removeCurrentDataSource() {Deque<String> deque = DATASOURCE_CONTEXT.get();deque.poll();if (deque.isEmpty()) {DATASOURCE_CONTEXT.remove();}}
}
单例构造限制
private DynamicDataSourceContextHolder() {if (DATASOURCE_CONTEXT != null) {throw new RuntimeException("禁止反射创建");}
}
  • 私有构造方法,防止外部实例化。
  • 添加了反射创建检测,防止通过反射破坏单例。
获取当前数据源
public static String getCurrentDataSource() {String peek = DATASOURCE_CONTEXT.get().peek();return Objects.isNull(peek) ? "" : peek;
}
  • 从当前线程的数据源栈中获取当前使用的数据源标识。
  • 如果栈为空,返回空字符串 "",避免后续操作(如 Map.get(null))导致空指针异常。
设置新数据源(入栈)
public static String addDataSource(String dds) {String datasource = StringUtils.isEmpty(dds) ? "" : dds;DATASOURCE_CONTEXT.get().push(datasource);return datasource;
}
  • 将指定的数据源标识压入栈顶。
  • 支持嵌套切换数据源(例如 AOP + 事务中嵌套注解切换)。
  • 如果传入 null 或空字符串,则使用默认空字符串。
 移除当前数据源(出栈)
public static void removeCurrentDataSource() {Deque<String> deque = DATASOURCE_CONTEXT.get();deque.poll();if (deque.isEmpty()) {DATASOURCE_CONTEXT.remove();}
}
  • 从栈中弹出一个数据源标识(LIFO)。
  • 如果栈为空,则清除整个线程局部变量,防止内存泄漏。

这个工具类通常用于配合 动态数据源路由类(如 AbstractRoutingDataSource)一起使用,实现多数据源切换。例如:

1. 动态数据源路由类(简化版) 

public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getCurrentDataSource();}
}

2. AOP 切面控制数据源切换

@Aspect
@Component
public class DataSourceAspect {@Before("@annotation(ds))")public void beforeSwitchDS(JoinPoint point, DynamicDataSource ds) {DynamicDataSourceContextHolder.addDataSource(ds.db());}@After("@annotation(ds))")public void afterSwitchDS(JoinPoint point, DynamicDataSource ds) {DynamicDataSourceContextHolder.removeCurrentDataSource();}
}

3. 注解定义

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicDataSource {String db() default "master";
}

4. Service 使用示例

@Service
public class UserService {@DynamicDataSource("slave1")public List<User> queryFromSlave() {return userMapper.selectAll();}public void insertUser(User user) {userMapper.insert(user);}
}


3. 测试

package com.imooc.cloud;import com.imooc.cloud.util.DynamicDataSourceContextHolder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;import java.util.List;@SpringBootTest
public class SpringDynamicTest {@Autowiredprivate JdbcTemplate jdbcTemplate;@Testpublic void testQueryUser() {DynamicDataSourceContextHolder.addDataSource("mybatisPlus");List list = jdbcTemplate.queryForList("select * from user");System.out.println("list: "+list);}@Testpublic void testQueryOrder() {DynamicDataSourceContextHolder.addDataSource("mybatisExample");List list = jdbcTemplate.queryForList("select * from `user`");System.out.println("list: "+list);}
}

4. 完整使用流程图

+-----------------+
| @DynamicDataSource("slave1") |
+-----------------+↓
+----------------------+
| AOP Before Advice    |
| DynamicDataSourceContextHolder.addDataSource("slave1") |
+----------------------+↓
+----------------------+
| AbstractRoutingDataSource.determineCurrentLookupKey() |
| return DynamicDataSourceContextHolder.getCurrentDataSource() |
+----------------------+↓
+----------------------+
| JDBC / MyBatis 使用对应数据源执行 SQL |
+----------------------+↓
+----------------------+
| AOP After Advice     |
| DynamicDataSourceContextHolder.removeCurrentDataSource() |
+----------------------+

5. 推荐使用 dynamic-datasource-spring-boot-starter

新项目,强烈建议使用开源组件来简化多数据源配置:

1. 引入依赖
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>4.2.0</version>
</dependency>
2. 配置文件(application.yml)
spring:datasource:dynamic:primary: masterdatasource:master:url: jdbc:mysql://localhost:3306/masterusername: rootpassword: rootslave1:url: jdbc:mysql://localhost:3306/slave1username: rootpassword: root
3. 使用注解
@DS("slave1")
public List<User> queryFromSlave() {return userMapper.selectList(null);
}

🎯 总结

功能说明
DynamicDataSourceContextHolder数据源上下文管理工具
Deque<String>支持嵌套切换
ThreadLocal线程隔离
AOP + 注解实现优雅的数据源切换
dynamic-datasource-spring-boot-starter推荐使用的封装库

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

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

相关文章

Kubernetes (K8S)知识详解

Kubernetes (K8S) 是什么&#xff1f; Kubernetes 是 Google 在 2014 年开源的生产级别的容器编排技术&#xff08;编排也可以简单理解为调度、管理&#xff09;&#xff0c;用于容器化应用的自动化部署、扩展和管理。它的前身是 Google 内部的 Borg 项目&#xff0c;Borg 是 …

在github上传python项目,然后在另外一台电脑下载下来后如何保障成功运行

如何在 GitHub 上传并在另一台电脑成功运行 Python 项目✅ 一、上传前&#xff08;本地准备&#xff09; 在你的项目文件夹中进行以下准备&#xff1a; 1. 确保结构清晰 my_project/ ├── main.py ├── utils.py ├── config.yaml ├── requirements.txt └── README…

详解Mysql Order by排序底层原理

MySQL 的 ORDER BY 子句实现排序是一个涉及查询优化、内存管理和磁盘 I/O 的复杂过程。其核心目标是高效地将结果集按照指定列和顺序排列。一、确定排序模式 (Sort Mode)MySQL 根据查询特性和系统变量决定采用哪种排序策略&#xff1a;1.1 Rowid 排序<sort_key, rowid> 模…

SpringBoot的介绍和项目搭建

SpringBoot是简化Spring应用开发的一个框架&#xff0c;他是Spring技术栈的整合。优点&#xff1a;能够快速创建独立运行的Spring项目以及与主流框架集成使用嵌入式的Servlet容器&#xff0c;应用无需打成war包&#xff0c;内嵌tomcatStarters自动依赖和版本控制大量的自动装配…

Selenium 攻略:从元素操作到 WebDriver 实战

在自动化测试、网页数据爬取、批量操作网页等场景中&#xff0c;Selenium 无疑是最受欢迎的工具之一。作为一款强大的 Web 自动化工具&#xff0c;它能模拟人类操作浏览器的行为&#xff0c;实现点击、输入、跳转等一系列动作。本文将从基础到进阶&#xff0c;全面解析 Seleniu…

【算法训练营Day14】二叉树part4

文章目录找树左下角的值路径总和总结&#xff1a;递归函数的返回值路径总和 II总结&#xff1a;二叉树递归的思考从中序与后序遍历序列构造二叉树找树左下角的值 题目链接&#xff1a;513. 找树左下角的值 解题逻辑&#xff1a; 使用层序遍历&#xff0c;将最后一层的第一个元…

工资系统如何计算工资

工资系统计算工资是一个集成数据收集、规则应用、自动核算和合规审核的自动化过程&#xff0c;以下是其核心原理和步骤&#xff0c;结合技术实现与法规要求进行说明&#xff1a;⚙️ 一、工资系统的基本框架与数据准备系统初始化与规则配置企业信息设置&#xff1a;录入公司名称…

车载通信架构 --- DoIP协议通信

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…

基于Event Sourcing和CQRS的微服务架构设计与实战

基于Event Sourcing和CQRS的微服务架构设计与实战 业务场景描述 在电商系统中&#xff0c;订单的高并发写入与复杂的状态流转&#xff08;下单、支付、发货、退货等&#xff09;给传统的CRUD模型带来了挑战&#xff1a; 数据一致性难保证&#xff1a;跨服务事务处理复杂&#x…

初级安全课第二次作业

&#xff08;一&#xff09;xss-labs 1~8关 1、前期准备 &#xff08;1&#xff09;打开小皮面板&#xff0c;并启动Apache和MySQL&#xff08;2&#xff09;将 xss-labs放到 phpstudy_pro 的 WWW 目录下&#xff08;3&#xff09;访问连接&#xff1a;http://localhost/xss-la…

从零搭建智能搜索代理:LangGraph + 实时搜索 + PDF导出完整项目实战

传统的AI聊天系统往往局限于预训练数据的知识范围&#xff0c;无法获取实时信息。本文将详细阐述如何构建一个基于LangGraph的智能代理系统&#xff0c;该系统能够智能判断何时需要进行网络搜索、有效维护对话上下文&#xff0c;并具备将对话内容导出为PDF文档的功能。 本系统…

C语言分支和循环语句——猜数字游戏

分支语句的语法形式1. if(表达式)语句;2. if(表达式)语句1;else语句2;3. Switch(表达式){ case 1: break;case 2: break;case 3: break; default: break; }循环语句的语法形式1. while(表达式)语句 ;2. for&#xff08;表达…

Python设计模式深度解析:原型模式(Prototype Pattern)完全指南

Python设计模式深度解析&#xff1a;原型模式&#xff08;Prototype Pattern&#xff09;完全指南前言什么是原型模式&#xff1f;模式的核心组成实际案例&#xff1a;游泳比赛管理系统游泳者数据结构原型模式的实现深拷贝 vs 浅拷贝&#xff1a;核心概念解析浅拷贝&#xff08…

SAP-ABAP:SAP万能长度计算:DYNAMIC_OUTPUT_LENGTH 深度解析

&#x1f4cf; SAP ABAP 万能长度计算&#xff1a;DYNAMIC_OUTPUT_LENGTH 深度解析核心作用&#xff1a;智能计算数据对象在列表/ALV中的实际显示宽度 | 关键优势&#xff1a;多字节字符处理 | 格式感知 | 动态适配&#x1f50d; 一、核心功能与技术特性 &#x1f4ca; 数据类型…

20250720-2-Kubernetes 调度-资源限制对Pod调度的影响(1)_笔记

一、创建一个Pod的工作流程&#xfeff;1. k8s架构解析&#xfeff;组件交互模式: Kubernetes采用list-watch机制的控制器架构&#xff0c;实现组件间交互的解耦。各组件通过监控自己负责的资源&#xff0c;当资源发生变化时由kube-apiserver通知相关组件。类比说明: 类似小卖铺…

mobaxteam x11传输界面避坑

mobaxteam x11传输界面避坑 文章目录mobaxteam x11传输界面避坑1 windows系统必须下载xing2 配置1 windows系统必须下载xing 因为windows系统本身没有x服务。 2 配置 如图

flink sql如何对hive string类型的时间戳进行排序

在 Flink SQL 中对 Hive 表的 STRING 类型时间戳进行排序&#xff0c;需要先将字符串转换为时间类型&#xff0c;再基于时间类型排序。以下是具体方法和示例&#xff1a; 一、核心解决方案 1. 字符串转 TIMESTAMP 后排序 若 Hive 中的时间戳格式为 yyyy-MM-dd HH:mm:ss&#xf…

Linux:线程控制

线程概念线程&#xff08;Thread&#xff09;是进程&#xff08;Process&#xff09; 中的一个执行单元&#xff0c;是操作系统能够进行运算调度的最小单位。线程也被称为“轻量级进程”&#xff08;Lightweight Process, LWP&#xff09;。一个进程可以包含多个线程&#xff0…

React 学习(4)

核心API———createRoot、render方法1.createRoot 方法是创建react的根容器&#xff0c;就是react元素的插入位置&#xff0c;插入的dom会被转化成react元素&#xff0c;根容器内的内容都会被react管理&#xff0c;原有dom都会被删除。react17 根容器创建、渲染方式&#xff0…

ASP .NET Core 8集成Swagger全攻略

Swagger (现在称为 OpenAPI) 是一个用于描述 RESTful API 的规范&#xff0c;ASP.NET Core 内置支持通过 Swashbuckle 库生成 Swagger 文档。以下是在 ASP.NET Core 8 中实现 Swagger 的完整步骤。1、添加Swagger NuGet 包dotnet add package Swashbuckle.AspNetCore2、添加Swa…