MyBatis實現(xiàn)多數據源動態(tài)切換的核心方法介紹
MyBatis 的數據源切換是構建高可用、高性能數據庫應用的關鍵技術。本文將從原理架構、核心代碼實現(xiàn)、用法詳解、初級進階以及優(yōu)勢劣勢等多個維度,結合具體代碼示例,深入剖析這一技術。
一、原理架構
MyBatis 的數據源管理核心在于 DataSource 接口的抽象與路由。在單數據源場景下,配置直接指向一個具體的數據庫連接池。而在多數據源場景下,我們需要引入一個"路由層",根據業(yè)務規(guī)則動態(tài)選擇真正的數據源。
1.1 核心架構組件
下圖展示了多數據源切換的整體架構,包括上下文管理、路由數據源以及實際的數據源工廠:
核心原理說明:
- Configuration/Environment:MyBatis 的全局配置,傳統(tǒng)方式下這里只能配置一個 Environment。
- DynamicDataSource:這是實現(xiàn)切換的關鍵。在 Spring 生態(tài)中,我們通常繼承
AbstractRoutingDataSource。它內部維護了一個Map<Object, Object>,存儲了 Key(如 “master”, “slave”)到真實DataSource的映射。 - DataSourceContextHolder:一個 ThreadLocal 工具類,用于在當前線程中存儲數據源的 Key,確保數據源選擇在同一個線程內是透傳的。
二、核心工作流程與代碼實現(xiàn)
數據源切換并不是發(fā)生在 SQL 解析階段,而是發(fā)生在獲取數據庫連接(Connection)的時刻。
2.1 工作流程時序圖
下圖展示了從 Service 層調用到獲取數據庫連接的完整時序:

2.2 關鍵代碼實現(xiàn)
要實現(xiàn)上述流程,我們需要編寫三個核心部分:上下文管理器、動態(tài)數據源類和配置類。
上下文管理器 (DataSourceContextHolder)
使用 ThreadLocal 保證線程隔離。
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 設置當前線程的數據源Key
*/
public static void setDataSourceKey(String key) {
CONTEXT_HOLDER.set(key);
}
/**
* 獲取當前線程的數據源Key
*/
public static String getDataSourceKey() {
return CONTEXT_HOLDER.get();
}
/**
* 清除當前線程的數據源Key(非常重要,防止內存泄漏和污染)
*/
public static void clearDataSourceKey() {
CONTEXT_HOLDER.remove();
}
}
動態(tài)數據源
繼承 Spring 的 AbstractRoutingDataSource,實現(xiàn)路由邏輯。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 直接從上下文中獲取Key
return DataSourceContextHolder.getDataSourceKey();
}
}
三、動態(tài)數據源實現(xiàn)架構與配置
下圖展示了我們在代碼中需要構建的類體系及其依賴關系:

3.1 Spring Boot 配置示例
我們需要配置多個真實的數據源 Bean,并將它們注入到 DynamicDataSource 中。
@Configuration
public class DataSourceConfig {
// 1. 配置主數據源
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
// 2. 配置從數據源
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
// 3. 配置動態(tài)數據源(Primary)
@Bean
@Primary
public DynamicDataSource dynamicDataSource(
@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave", slaveDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 設置目標數據源映射
dynamicDataSource.setTargetDataSources(targetDataSources);
// 設置默認數據源(通常為主庫)
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
return dynamicDataSource;
}
}
四、數據源切換策略與用法
如何決定使用哪個數據源?常見的策略包括注解驅動、方法名約定和手動切換。
下圖梳理了常見的路由策略:

4.1 策略一:自定義注解 + AOP(推薦)
這是最優(yōu)雅的方式,對業(yè)務代碼侵入性最小。
第一步:定義注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value() default "master"; // 默認主庫
}
第二步:定義 AOP 切面
@Aspect
@Component
public class DataSourceAspect {
// 攔截帶有 @TargetDataSource 注解的方法
@Before("@annotation(targetDataSource)")
public void before(JoinPoint point, TargetDataSource targetDataSource) {
String dataSourceKey = targetDataSource.value();
if (StringUtils.isNotBlank(dataSourceKey)) {
DataSourceContextHolder.setDataSourceKey(dataSourceKey);
System.out.println("切換數據源到: " + dataSourceKey);
}
}
// 執(zhí)行完畢后清除上下文
@After("@annotation(targetDataSource)")
public void after(TargetDataSource targetDataSource) {
DataSourceContextHolder.clearDataSourceKey();
}
}
第三步:業(yè)務使用
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 寫操作走主庫
@TargetDataSource("master")
public void addUser(User user) {
userMapper.insert(user);
}
// 讀操作走從庫
@TargetDataSource("slave")
public User getUserById(Long id) {
return userMapper.selectById(id);
}
}
4.2 策略二:手動編程式切換
在某些復雜的動態(tài)邏輯中(例如根據前端傳入的參數決定租戶庫),可能需要手動切換。
public void processOrder(Long userId, Order order) {
try {
// 1. 根據用戶ID計算分片key,切換到對應的用戶庫
String shardKey = "shard_" + (userId % 4);
DataSourceContextHolder.setDataSourceKey(shardKey);
// 2. 執(zhí)行業(yè)務操作(此時會路由到指定分片)
orderMapper.insert(order);
} finally {
// 3. 務必在finally中清理,避免影響后續(xù)操作
DataSourceContextHolder.clearDataSourceKey();
}
}
五、進階場景:讀寫分離架構
讀寫分離是多數據源最經典的應用場景。應用層通過 AOP 或攔截器,自動將 SELECT 請求發(fā)送至從庫,INSERT/UPDATE/DELETE 發(fā)送至主庫。下圖展示了經典的讀寫分離架構:

實現(xiàn)思路:可以結合 MyBatis 的 Plugin(插件)機制,在 SQL 執(zhí)行前解析 SQL 語句類型。如果以 SELECT 開頭,則調用DataSourceContextHolder.setDataSourceKey("slave"),否則設為 master。
六、優(yōu)勢與劣勢分析
任何技術方案都有其適用場景。下圖詳細對比了該技術的優(yōu)勢與劣勢:

6.1 劣勢詳解:事務邊界問題
這是多數據源最大的坑。Spring 的 @Transactional 注解是基于線程綁定的 Connection 來管理的。 如果在一個事務方法中切換了數據源,Spring 的事務管理器可能無法感知,或者導致事務失效。
錯誤示例:
@Transactional // 開啟事務,此時獲取了 Master 的連接
public void updateAndRead() {
userMapper.updateUser(user); // 使用 Master 連接
// 嘗試切換數據源
DataSourceContextHolder.setDataSourceKey("slave");
// 問題:事務管理器仍持有 Master 的連接,這里可能并不會真的切換,
// 或者拋出異常,因為 Connection 已經綁定在事務中了。
userMapper.selectUser(id);
}
解決方案:
- 避免事務內切換:將讀寫操作拆分到不同的事務方法中。
- 編程式事務:使用
TransactionTemplate精確控制事務邊界。 - 分布式事務:如果必須跨庫操作(如 Master 寫,Slave 讀并校驗),需要引入 Seata 等分布式事務框架,但這通常用于跨庫寫,讀寫分離場景下通常允許最終一致性。
七、配置實現(xiàn)全流程
下圖描述了從零開始搭建動態(tài)數據源的完整配置步驟:

八、進階應用場景與演進
隨著業(yè)務復雜度的提升,數據源切換技術也衍生出了多種高級應用場景。
下圖展示了多數據源技術在不同階段的應用場景及演進:

技術演進時間線:

九、總結
MyBatis 的數據源切換機制通過 AbstractRoutingDataSource 和 ThreadLocal 的巧妙結合,為我們提供了一種輕量級、靈活的數據路由方案。核心要點回顧:
- ThreadLocal 是靈魂:它保證了在多線程環(huán)境下,每個請求的數據源選擇互不干擾。
- AOP 是翅膀:通過切面將數據源選擇邏輯從業(yè)務代碼中剝離,保持代碼整潔。
- 事務是雙刃劍:在使用多數據源時,務必清醒地認識事務邊界的限制,避免事務失效。
在實際項目中,建議從簡單的讀寫分離開始,逐步根據業(yè)務需求引入更復雜的路由策略,并做好充分的監(jiān)控與容錯準備。
以上就是MyBatis實現(xiàn)多數據源動態(tài)切換的核心方法介紹的詳細內容,更多關于MyBatis數據源切換的資料請關注腳本之家其它相關文章!
相關文章
Spring Boot整合Mybatis并完成CRUD操作的實現(xiàn)示例
這篇文章主要介紹了Spring Boot整合Mybatis并完成CRUD操作的實現(xiàn)示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
Spring @Cacheable自定義緩存過期時間的實現(xiàn)示例
本文主要介紹了Spring @Cacheable自定義緩存過期時間的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-05-05
SpringQuartz集群支持JDBC存儲與分布式執(zhí)行的最佳實踐
SpringQuartz集群通過JDBC存儲和分布式執(zhí)行機制,有效解決了單點故障和擴展性問題,本文將詳細介紹SpringQuartz集群支持的實現(xiàn)原理、配置方法和最佳實踐,助力開發(fā)者構建穩(wěn)定可靠的分布式調度系統(tǒng),感興趣的朋友一起看看吧2025-04-04
SpringBoot圖文并茂詳解如何引入mybatis與連接Mysql數據庫
這篇文章主要介紹了SpringBoot如何引入mybatis與連接Mysql數據庫,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07

