使用 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
英文地址,請注明出處:http://en.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…