使用AbstractRoutingDataSource實(shí)現(xiàn)數(shù)據(jù)源動(dòng)態(tài)切換的實(shí)例
實(shí)現(xiàn)原理
AbstractRoutingDataSource 繼承自 Spring 的 AbstractDataSource 類,并實(shí)現(xiàn)了 DataSource 接口。它內(nèi)部維護(hù)了一個(gè)映射(Map),用于存儲(chǔ)數(shù)據(jù)源標(biāo)識(shí)和對(duì)應(yīng)的數(shù)據(jù)源實(shí)例。當(dāng)需要獲取連接時(shí),AbstractRoutingDataSource 會(huì)調(diào)用一個(gè)抽象方法 determineCurrentLookupKey() 來確定當(dāng)前的數(shù)據(jù)源標(biāo)識(shí),然后根據(jù)這個(gè)標(biāo)識(shí)從映射中獲取對(duì)應(yīng)的數(shù)據(jù)源,并從該數(shù)據(jù)源中獲取連接。
關(guān)鍵方法解析
determineCurrentLookupKey(): 這是一個(gè)抽象方法,需要用戶自己實(shí)現(xiàn)。這個(gè)方法用于確定當(dāng)前的數(shù)據(jù)源標(biāo)識(shí),其返回值將用作查找映射中對(duì)應(yīng)數(shù)據(jù)源的關(guān)鍵字。通常,可以在這個(gè)方法中獲取當(dāng)前線程的一些狀態(tài)或上下文信息,來動(dòng)態(tài)決定使用哪個(gè)數(shù)據(jù)源。
afterPropertiesSet(): 這個(gè)方法是在設(shè)置完所有屬性后被調(diào)用的。在這個(gè)方法中,
AbstractRoutingDataSource會(huì)驗(yàn)證數(shù)據(jù)源映射是否已經(jīng)被正確設(shè)置,并且如果設(shè)置了默認(rèn)數(shù)據(jù)源,也會(huì)進(jìn)行檢查。getConnection() 和 getConnection(String username, String password): 這兩個(gè)方法是
DataSource接口要求實(shí)現(xiàn)的方法。在AbstractRoutingDataSource中,這兩個(gè)方法的實(shí)現(xiàn)邏輯是先調(diào)用determineCurrentLookupKey()方法獲取當(dāng)前數(shù)據(jù)源標(biāo)識(shí),然后根據(jù)這個(gè)標(biāo)識(shí)從映射中找到對(duì)應(yīng)的數(shù)據(jù)源,并調(diào)用該數(shù)據(jù)源的getConnection()方法獲取連接。
場(chǎng)景實(shí)例
我們?cè)O(shè)計(jì)一個(gè)這樣的場(chǎng)景,比如一個(gè)多租戶(multi-tenant)應(yīng)用,其中每個(gè)租戶都有自己的數(shù)據(jù)庫。在這個(gè)場(chǎng)景中,系統(tǒng)根據(jù)當(dāng)前用戶的租戶ID動(dòng)態(tài)切換數(shù)據(jù)源。我們將使用 Spring Boot、Spring Data JPA 和 AbstractRoutingDataSource 來實(shí)現(xiàn)這個(gè)場(chǎng)景。
步驟 1: 定義租戶識(shí)別邏輯
首先,我們需要一個(gè)機(jī)制來識(shí)別當(dāng)前請(qǐng)求屬于哪個(gè)租戶。假設(shè)每個(gè)請(qǐng)求的HTTP頭部都包含一個(gè)X-TenantID字段來標(biāo)識(shí)租戶ID:
@Component
public class TenantInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String tenantId = request.getHeader("X-TenantID");
if (tenantId != null) {
TenantContext.setCurrentTenant(tenantId);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
TenantContext.clear();
}
}
在這里,TenantContext是一個(gè)存儲(chǔ)當(dāng)前線程租戶ID的類:
public class TenantContext {
private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
public static void setCurrentTenant(String tenantId) {
currentTenant.set(tenantId);
}
public static String getCurrentTenant() {
return currentTenant.get();
}
public static void clear() {
currentTenant.remove();
}
}
步驟 2: 配置動(dòng)態(tài)數(shù)據(jù)源
接下來,我們需要配置動(dòng)態(tài)數(shù)據(jù)源來根據(jù)租戶ID選擇正確的數(shù)據(jù)庫:
public class MultiTenantDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getCurrentTenant();
}
}
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
MultiTenantDataSource dataSource = new MultiTenantDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
// 假設(shè)已經(jīng)有一個(gè)方法來獲取所有租戶的數(shù)據(jù)源配置
Map<String, DataSource> tenantDataSources = tenantDataSources();
targetDataSources.putAll(tenantDataSources);
dataSource.setTargetDataSources(targetDataSources);
dataSource.setDefaultTargetDataSource(defaultDataSource()); // 默認(rèn)數(shù)據(jù)源
dataSource.afterPropertiesSet();
return dataSource;
}
private Map<String, DataSource> tenantDataSources() {
// 這里應(yīng)該從配置文件或數(shù)據(jù)庫中加載所有租戶的數(shù)據(jù)源配置
// 舉例,假設(shè)有兩個(gè)租戶tenantA和tenantB
Map<String, DataSource> result = new HashMap<>();
result.put("tenantA", createDataSource("jdbc:mysql://localhost:3306/tenantA"));
result.put("tenantB", createDataSource("jdbc:mysql://localhost:3306/tenantB"));
return result;
}
private DataSource createDataSource(String url) {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl(url);
dataSource.setUsername("username");
dataSource.setPassword("password");
return dataSource;
}
private DataSource defaultDataSource() {
// 定義默認(rèn)數(shù)據(jù)源
return createDataSource("jdbc:mysql://localhost:3306/defaultDb");
}
}
步驟 3: 集成到Spring MVC
最后,我們需要確保每個(gè)請(qǐng)求都會(huì)通過我們的TenantInterceptor,以便正確設(shè)置租戶上下文:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private TenantInterceptor tenantInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tenantInterceptor);
}
}
最后
以上就是使用AbstractRoutingDataSource實(shí)現(xiàn)數(shù)據(jù)源動(dòng)態(tài)切換的實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于AbstractRoutingDataSource數(shù)據(jù)源切換的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot+ThreadLocal+AbstractRoutingDataSource實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源
- SpringBoot基于AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源動(dòng)態(tài)切換
- Spring(AbstractRoutingDataSource)實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換示例
- Spring AbstractRoutingDatasource 動(dòng)態(tài)數(shù)據(jù)源的實(shí)例講解
相關(guān)文章
JetBrains IntelliJ IDEA 優(yōu)化教超詳細(xì)程
這篇文章主要介紹了JetBrains IntelliJ IDEA 優(yōu)化教超詳細(xì)程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
Java C++實(shí)現(xiàn)相同MD5加密算法的方式
這篇文章主要介紹了Java與C++實(shí)現(xiàn)相同MD5加密算法的方法,需要的朋友可以參考下面文章內(nèi)容2021-09-09
SpringBoot3整合SpringSecurity6快速入門示例教程
SpringSecurity 是Spring大家族中一名重要成員,是專門負(fù)責(zé)安全的框架,本文給大家介紹SpringBoot3整合SpringSecurity6快速入門示例教程,感興趣的朋友一起看看吧2025-04-04
Java實(shí)現(xiàn)時(shí)間與字符串互相轉(zhuǎn)換詳解
這篇文章主要為大家詳細(xì)介紹了Java中實(shí)現(xiàn)時(shí)間與字符串互相轉(zhuǎn)換的相關(guān)方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04
SpringSecurity?鑒權(quán)與授權(quán)的具體使用
本文介紹了Spring?Security在前后端分離架構(gòu)中的核心應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-07-07
Spring中配置文件XML驗(yàn)證錯(cuò)誤全面解決指南
這篇文章主要為大家詳細(xì)介紹了Spring中配置文件XML驗(yàn)證錯(cuò)誤全面解決方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-11-11

