Spring數(shù)據(jù)源動態(tài)切換

Spring數(shù)據(jù)源動態(tài)切換

利用spring-jdbc包中的AbstractRoutingDataSource類愚臀,從名字就可以看出來這是個抽象類。

先來看看這個類的相關(guān)類圖

spring動態(tài)數(shù)據(jù)源類圖.png

可以看到這個類實現(xiàn)了DataSource接口,這意味著其繼承類可以在任何需要DataSource的地方使用镐依。

DataSource接口中只定義了兩個方法

Connection getConnection() throws SQLException;

Connection getConnection(String username, String password)
    throws SQLException;

所以我們重點關(guān)注AbstractRoutingDataSource中如何實現(xiàn)這兩個方法甩挫。

    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }

繼續(xù)追蹤

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;
    }

這個方法被設(shè)計成protected,說明子類可以重寫炮障。該方法中調(diào)用了AbstractRoutingDataSource中唯一一個抽象方法

/**
     * Determine the current lookup key. This will typically be
     * implemented to check a thread-bound transaction context.
     * <p>Allows for arbitrary keys. The returned key needs
     * to match the stored lookup key type, as resolved by the
     * {@link #resolveSpecifiedLookupKey} method.
     */
    protected abstract Object determineCurrentLookupKey();

這個方法便是我們實現(xiàn)動態(tài)數(shù)據(jù)源切換的關(guān)鍵了目派。

determineTargetDataSource中可以看到最終使用的DataSource對象是從resolvedDataSources中獲取的。其定義如下:

private Map<Object, DataSource> resolvedDataSources;

那么這個map是什么時候填充的呢胁赢?

   @Override
    public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        }
        this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
        for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
            Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
            DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
            this.resolvedDataSources.put(lookupKey, dataSource);
        }
        if (this.defaultTargetDataSource != null) {
            this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
        }
    }

可以清晰的看到是用targetDataSources里面的數(shù)據(jù)填充的企蹭。

接下來就是最終的解決方案了。

  1. 定義數(shù)據(jù)源
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
    
        String datasource = ContextHolder.getCustomerType();
        return datasource;
    }
}
<bean id="dataSource" class="com.sogou.daohang.datamonitor.support.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="qidian" value-ref="test1" />
                <entry key="iguess" value-ref="test2" />
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="default" />
    </bean>

其中test1,test2以及default都是我們定義的DataSource bean。

  1. 動態(tài)數(shù)據(jù)源操作
public class ContextHolder {


  public static final String DATA_SOURCE_TEST1 = "test1";

  /**
   * 猜你喜歡數(shù)據(jù)庫
   */
  public static final String DATA_SOURCE_TEST2 = "test2";

  /**
   * 當(dāng)前系統(tǒng)的數(shù)據(jù)庫
   */
  public static final String DATA_SOURCE_DEFAULT = "default";

  // 利用ThreadLocal解決線程安全問題
  private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

  public static void setCustomerType(String customerType) {

    contextHolder.set(customerType);
  }

  public static String getCustomerType() {

    return contextHolder.get();
  }

  public static void clearCustomerType() {

    contextHolder.remove();
  }
}
  1. 定義切面
public class DataSourceInterceptor {

    public void setDefaultDataSource() {
        ContextHolder.setCustomerType(ContextHolder.DATA_SOURCE_DEFAULT);
    }

    public void setTest1DataSource() {
        ContextHolder.setCustomerType(ContextHolder.DATA_SOURCE_TEST1);
    }

    public void setTest2DataSource() {
        ContextHolder.setCustomerType(ContextHolder.DATA_SOURCE_TEST2);
    }
}
<!-- 動態(tài)數(shù)據(jù)源切換aop 先與事務(wù)的aop  -->
    <bean id="dataSourceInterceptor" class="com.*.support.DataSourceInterceptor" />

    <aop:config>
        <aop:pointcut id="test1Pointcut" expression="execution(* com.*.dao.test1..*.*(..))" />
        <aop:pointcut id="test2Pointcut" expression="execution(* com.*.dao.test2..*.*(..))" />
        <aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
            <aop:before method="setDataSourceTest1" pointcut-ref="test1Pointcut"/>
            <aop:before method="setDataSourceTest2" pointcut-ref="test2Pointcut"/>
        </aop:aspect>
    </aop:config>

這樣在調(diào)用相應(yīng)的方法時就能自動切換數(shù)據(jù)源了练对,但是設(shè)置完畢數(shù)據(jù)源后并沒有清除遍蟋,這意味著需要手動清除∶荆可以加上aop after在每個方法調(diào)用后清除掉當(dāng)前的數(shù)據(jù)源虚青。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市螺男,隨后出現(xiàn)的幾起案子棒厘,更是在濱河造成了極大的恐慌,老刑警劉巖下隧,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奢人,死亡現(xiàn)場離奇詭異,居然都是意外死亡淆院,警方通過查閱死者的電腦和手機何乎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來土辩,“玉大人支救,你說我怎么就攤上這事】教裕” “怎么了各墨?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長启涯。 經(jīng)常有香客問我贬堵,道長,這世上最難降的妖魔是什么结洼? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任黎做,我火速辦了婚禮,結(jié)果婚禮上补君,老公的妹妹穿的比我還像新娘引几。我一直安慰自己,他們只是感情好挽铁,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布伟桅。 她就那樣靜靜地躺著,像睡著了一般叽掘。 火紅的嫁衣襯著肌膚如雪楣铁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天更扁,我揣著相機與錄音盖腕,去河邊找鬼赫冬。 笑死,一個胖子當(dāng)著我的面吹牛溃列,可吹牛的內(nèi)容都是我干的劲厌。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼听隐,長吁一口氣:“原來是場噩夢啊……” “哼补鼻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起雅任,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤风范,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后沪么,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硼婿,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年禽车,在試婚紗的時候發(fā)現(xiàn)自己被綠了寇漫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡哭当,死狀恐怖猪腕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钦勘,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布亚亲,位于F島的核電站彻采,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捌归。R本人自食惡果不足惜肛响,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惜索。 院中可真熱鬧特笋,春花似錦、人聲如沸巾兆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽角塑。三九已至蔫磨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間圃伶,已是汗流浹背堤如。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工蒲列, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人搀罢。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓蝗岖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親榔至。 傳聞我的和親對象是個殘疾皇子抵赢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)洛退,斷路器瓣俯,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342
  • application的配置屬性。 這些屬性是否生效取決于對應(yīng)的組件是否聲明為Spring應(yīng)用程序上下文里的Bea...
    新簽名閱讀 5,353評論 1 27
  • 這些屬性是否生效取決于對應(yīng)的組件是否聲明為 Spring 應(yīng)用程序上下文里的 Bean(基本是自動配置的)兵怯,為一個...
    發(fā)光的魚閱讀 1,418評論 0 14
  • 瞬息間是夜晚 —夸西莫多(意大利) 每一個人 偎依著大地的胸懷 孤寂地裸露在陽光之下: 瞬息間是夜晚彩匕。
    花仙樂閱讀 316評論 0 0