MyBatis動(dòng)態(tài)數(shù)據(jù)源切換的完整方案
在微服務(wù)架構(gòu)和分布式系統(tǒng)中,動(dòng)態(tài)數(shù)據(jù)源是應(yīng)對(duì)多環(huán)境切換、讀寫(xiě)分離、多租戶等復(fù)雜場(chǎng)景的核心工具。MyBatis 與 Spring 結(jié)合后,通過(guò) AbstractRoutingDataSource 和 線程上下文管理(ThreadLocal),可以靈活實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)切換。以下是實(shí)現(xiàn) MyBatis 動(dòng)態(tài)數(shù)據(jù)源的完整方案,涵蓋核心步驟、代碼示例和最佳實(shí)踐。
1. 核心概念
- 動(dòng)態(tài)數(shù)據(jù)源:根據(jù)業(yè)務(wù)需求自動(dòng)選擇不同的數(shù)據(jù)庫(kù)連接(如主庫(kù)、從庫(kù)、測(cè)試庫(kù))。
AbstractRoutingDataSource:Spring 提供的抽象類,負(fù)責(zé)動(dòng)態(tài)決定當(dāng)前使用哪個(gè)數(shù)據(jù)源。ThreadLocal:線程安全的上下文管理工具,保存當(dāng)前線程的數(shù)據(jù)源標(biāo)識(shí)(如master、slave)。
2. 實(shí)現(xiàn)步驟
(1) 配置多個(gè)數(shù)據(jù)源
在 application.yml 或 application.properties 中定義多個(gè)數(shù)據(jù)源配置:
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
(2) 創(chuàng)建動(dòng)態(tài)數(shù)據(jù)源類
繼承 AbstractRoutingDataSource,重寫(xiě) determineCurrentLookupKey 方法:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 從線程上下文中獲取當(dāng)前數(shù)據(jù)源標(biāo)識(shí)
return DataSourceContextHolder.getDataSource();
}
}
(3) 數(shù)據(jù)源上下文管理
使用 ThreadLocal 保存當(dāng)前線程的數(shù)據(jù)源標(biāo)識(shí):
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
(4) 配置動(dòng)態(tài)數(shù)據(jù)源 Bean
在 Spring 配置類中定義動(dòng)態(tài)數(shù)據(jù)源,并綁定多個(gè)數(shù)據(jù)源:
@Configuration
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource 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); // 默認(rèn)使用主數(shù)據(jù)源
return dynamicDataSource;
}
}
(5) 整合 MyBatis
配置 SqlSessionFactory 和 MapperScannerConfigurer,確保使用動(dòng)態(tài)數(shù)據(jù)源:
@Configuration
@MapperScan(basePackages = "com.example.mapper")
public class MyBatisConfig {
@Autowired
private DataSource dynamicDataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dynamicDataSource);
return factoryBean.getObject();
}
}
(6) 動(dòng)態(tài)切換數(shù)據(jù)源
在業(yè)務(wù)代碼中通過(guò) DataSourceContextHolder 設(shè)置數(shù)據(jù)源標(biāo)識(shí):
// 切換到從數(shù)據(jù)源(讀操作)
DataSourceContextHolder.setDataSource("slave");
try {
// 執(zhí)行查詢操作
List<User> users = userMapper.selectAll();
} finally {
// 清除線程上下文
DataSourceContextHolder.clearDataSource();
}
// 切換到主數(shù)據(jù)源(寫(xiě)操作)
DataSourceContextHolder.setDataSource("master");
try {
userMapper.insert(user);
} finally {
DataSourceContextHolder.clearDataSource();
}
(7) 事務(wù)管理
為每個(gè)數(shù)據(jù)源配置獨(dú)立的事務(wù)管理器,并通過(guò) @Transactional 注解指定事務(wù)管理器:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean(name = "masterTransactionManager")
public PlatformTransactionManager masterTransactionManager(
@Qualifier("masterDataSource") DataSource masterDataSource) {
return new DataSourceTransactionManager(masterDataSource);
}
@Bean(name = "slaveTransactionManager")
public PlatformTransactionManager slaveTransactionManager(
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
return new DataSourceTransactionManager(slaveDataSource);
}
}
// 在 Service 層指定事務(wù)管理器
@Service
public class UserService {
@Transactional("masterTransactionManager")
public void insertUser(User user) {
userMapper.insert(user);
}
@Transactional("slaveTransactionManager")
public List<User> selectAllUsers() {
return userMapper.selectAll();
}
}
3. 使用場(chǎng)景
讀寫(xiě)分離
- 寫(xiě)操作(
INSERT/UPDATE/DELETE)使用主庫(kù),讀操作(SELECT)使用從庫(kù)。 - 示例:訂單寫(xiě)入主庫(kù),訂單查詢從庫(kù)。
多環(huán)境切換
- 開(kāi)發(fā)環(huán)境、測(cè)試環(huán)境、生產(chǎn)環(huán)境分別綁定不同的數(shù)據(jù)源。
- 示例:通過(guò)配置文件切換不同數(shù)據(jù)庫(kù)地址。
多租戶隔離
- 每個(gè)租戶使用獨(dú)立的數(shù)據(jù)源,通過(guò)租戶標(biāo)識(shí)動(dòng)態(tài)切換。
- 示例:企業(yè)級(jí) SaaS 系統(tǒng),不同企業(yè)訪問(wèn)不同數(shù)據(jù)庫(kù)。
4. 注意事項(xiàng)
線程安全
- 確保
ThreadLocal在異步任務(wù)中傳遞數(shù)據(jù)源標(biāo)識(shí)(如使用InheritableThreadLocal或TransmittableThreadLocal)。
事務(wù)一致性
- 跨數(shù)據(jù)源的事務(wù)需謹(jǐn)慎處理,避免臟讀或數(shù)據(jù)不一致??墒褂梅植际绞聞?wù)框架(如 Seata)。
性能優(yōu)化
- 避免頻繁切換數(shù)據(jù)源,合理設(shè)計(jì)讀寫(xiě)分離策略。
異常處理
- 捕獲數(shù)據(jù)源切換異常,提供回退機(jī)制(如默認(rèn)使用主數(shù)據(jù)源)。
5. 總結(jié)
| 特性 | 實(shí)現(xiàn)方式 |
|---|---|
| 動(dòng)態(tài)切換 | AbstractRoutingDataSource + ThreadLocal |
| 讀寫(xiě)分離 | 通過(guò)業(yè)務(wù)邏輯判斷操作類型,設(shè)置 master/slave |
| 多環(huán)境支持 | 通過(guò)配置文件區(qū)分不同環(huán)境的數(shù)據(jù)源 |
| 事務(wù)管理 | 為每個(gè)數(shù)據(jù)源配置獨(dú)立的 PlatformTransactionManager |
| 線程安全 | 使用 ThreadLocal 保存上下文,異步場(chǎng)景需傳遞上下文 |
通過(guò)以上方案,MyBatis 動(dòng)態(tài)數(shù)據(jù)源可以靈活適配多種業(yè)務(wù)場(chǎng)景,成為多環(huán)境切換的“神器”。結(jié)合 Spring 的強(qiáng)大生態(tài),開(kāi)發(fā)者可以輕松實(shí)現(xiàn)高可用、高性能的分布式系統(tǒng)。
以上就是MyBatis動(dòng)態(tài)數(shù)據(jù)源切換的完整方案的詳細(xì)內(nèi)容,更多關(guān)于MyBatis動(dòng)態(tài)數(shù)據(jù)源切換的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JDK8接口的默認(rèn)與靜態(tài)方法-接口與抽象類的區(qū)別詳解
這篇文章主要介紹了JDK8接口的默認(rèn)與靜態(tài)方法-接口與抽象類的區(qū)別詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下2019-06-06
如何用Dos命令運(yùn)行Java版HelloWorld你知道嗎
這篇文章主要介紹了在dos窗口中編譯和運(yùn)行java文件的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
關(guān)于Java三大特性之多態(tài)的總結(jié)
這篇文章主要介紹了關(guān)于Java三大特性之多態(tài)的總結(jié),內(nèi)容詳細(xì),涉及多態(tài)的定義,存在條件,好處,分類及實(shí)現(xiàn)方式等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
Spring?Boot如何排除自動(dòng)加載數(shù)據(jù)源
這篇文章主要介紹了Spring?Boot如何排除自動(dòng)加載數(shù)據(jù)源,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
在Spring中利用@Order注解對(duì)bean和依賴進(jìn)行排序
在Spring框架中,@Order是一個(gè)經(jīng)常被忽視但非常重要的注解,在項(xiàng)目開(kāi)發(fā)中,當(dāng)我們需要維護(hù)bean的特定順序或者存在許多相同類型的bean時(shí),這個(gè)注解就發(fā)揮了作用,這篇文章講的就是如何利用@Order注解對(duì)bean和依賴進(jìn)行排序,需要的朋友可以參考下2023-11-11

