Spring實(shí)現(xiàn)多數(shù)據(jù)源動(dòng)態(tài)切換

背景

隨著業(yè)務(wù)的發(fā)展,數(shù)據(jù)庫壓力的增大,如何分割數(shù)據(jù)庫的讀寫壓力是我們需要考慮的問題,而能夠動(dòng)態(tài)的切換數(shù)據(jù)源就是我們的首要目標(biāo)。


基礎(chǔ)

Spring作為我們項(xiàng)目的應(yīng)用容器,也對這方面提供了很好的支持,當(dāng)我們的持久化框架需要數(shù)據(jù)庫連接時(shí),我們需要做到動(dòng)態(tài)的切換數(shù)據(jù)源,這些Spring的AbstractRoutingDataSource都給我們留了拓展的空間,可以先來看看抽象類AbstractRoutingDataSource在獲取數(shù)據(jù)庫連接時(shí)做了什么

private Map<Object, DataSource> resolvedDataSources; //從配置文件讀取到的DataSources的Map
 
private DataSource resolvedDefaultDataSource; //默認(rèn)數(shù)據(jù)源
  
public Connection getConnection() throws SQLException {
   return determineTargetDataSource().getConnection();
}
 
public Connection getConnection(String username, String password) throws SQLException {
   return determineTargetDataSource().getConnection(username, password);
}
  
protected DataSource determineTargetDataSource() {
   Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
   Object lookupKey = determineCurrentLookupKey();
   DataSource dataSource = this.resolvedDataSources.get(lookupKey);
   if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
      dataSource = this.resolvedDefaultDataSource;
   }
   if (dataSource == null) {
      throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
   }
   return dataSource;
}
  
protected abstract Object determineCurrentLookupKey();

可以看到AbstractRoutingDataSource在決定目標(biāo)數(shù)據(jù)源的時(shí)候,會先調(diào)用determineCurrentLookupKey()方法得到一個(gè)key,我們通過這個(gè)key從配置好的resolvedDataSources(Map結(jié)構(gòu))拿到這次調(diào)用對應(yīng)的數(shù)據(jù)源,而determineCurrentLookupKey()開放出來讓我們實(shí)現(xiàn)


實(shí)現(xiàn)

前面提到我們可以通過實(shí)現(xiàn)AbstractRoutingDataSource的determineCurrentLookupKey()方法來決定這次調(diào)用的數(shù)據(jù)源。首先說一下思路:當(dāng)我們的一個(gè)線程需要針對數(shù)據(jù)庫做一系列操作時(shí),每次都會去調(diào)用getConnection()方法獲取數(shù)據(jù)庫連接,然后執(zhí)行完后再釋放或歸還數(shù)據(jù)庫連接(SqlSessionTemplate就是這么做的),那么很明顯,我們需要能夠保證每次調(diào)用Dao層方法時(shí)都能動(dòng)態(tài)的切換數(shù)據(jù)源,這就需要Spring的AOP:我們定義一個(gè)切面,當(dāng)我們調(diào)用Dao層方法時(shí),執(zhí)行我們的邏輯來判斷這次調(diào)用的數(shù)據(jù)源,AOP切面定義如下:

image.png
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {
    String value();
}
  
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
 
    Signature signature = jp.getSignature();
 
    String dataSourceKey = getDataSourceKey(signature);
 
    if (StringUtils.hasText(dataSourceKey)) {
        MyDataSource.setDataSourceKey(dataSourceKey);
    }
 
    Object jpVal = jp.proceed();
 
    return jpVal;
}
  
public String getDataSourceKey(Signature signature) {
    if (signature == null) return null;
 
    if (signature instanceof MethodSignature) {
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method.isAnnotationPresent(DataSource.class)) {
            return 方法的注解值;
        }
 
        Class declaringClazz = method.getDeclaringClass();
        if (declaringClazz.isAnnotationPresent(DataSource.class)) {
            return 類級別的注解值;
        }
 
        Package pkg = declaringClazz.getPackage();
        return 該包路徑的默認(rèn)數(shù)據(jù)源;
    }
 
    return null;
}

這里我們就可以得到我們需要的數(shù)據(jù)源,現(xiàn)在就是如何保存這個(gè)值了,因?yàn)檫@個(gè)值是我們這個(gè)線程才需要使用的,所以綜合考慮聲明一個(gè)ThreadLocal<String>來保存不同線程的不同值,目前已經(jīng)解決了當(dāng)前調(diào)用方法的數(shù)據(jù)源和數(shù)據(jù)源值的保存了,那么回到前面AbstractRoutingDataSource的determineTargetDataSource()方法中,我們就可以重寫抽象方法determineCurrentLookupKey,返回我們剛剛保存的數(shù)據(jù)源的值,代碼如下:

public class MyDataSource extends AbstractRoutingDataSource {
 
    private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<String>();
 
    public static void setDataSourceKey(String dataSource) {
        dataSourceKey.set(dataSource);
    }
 
    protected Object determineCurrentLookupKey() {
        String dsName = dataSourceKey.get();
        dataSourceKey.remove(); //這里需要注意的時(shí),每次我們返回當(dāng)前數(shù)據(jù)源的值得時(shí)候都需要移除ThreadLocal的值,這是為了避免同一線程上一次方法調(diào)用對之后調(diào)用的影響
        return dsName;
    }
 
}

總結(jié)

大體的Spring實(shí)現(xiàn)多數(shù)據(jù)源的動(dòng)態(tài)切換思路如上

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,658評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,282評論 6 342
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,226評論 25 708
  • 是生命法則的一部分 天以財(cái)來行道 以滋養(yǎng)萬物 人以財(cái)來替天行道 財(cái)來滋養(yǎng)生命 眼睛看到的并不是真相 想要更多財(cái)富 ...
    axjl如意閱讀 436評論 0 0
  • 在我很小的時(shí)候,我就喜歡很早的起來。特別是夏天,我跑到外面自己玩耍,天微涼,路很靜。 現(xiàn)在,我依然喜歡早起。我喜歡...
    馬烈視界閱讀 126評論 0 0

友情鏈接更多精彩內(nèi)容