technology-integration(五)---SpringBoot多數(shù)據(jù)源+AbstractRoutingDataSource源碼解析

零入侵構(gòu)造多數(shù)據(jù)源

??中大型項(xiàng)目常常會(huì)配有多個(gè)數(shù)據(jù)庫(kù)昧捷,而需要使用數(shù)據(jù)庫(kù)做curd的我們需要一種能很好的解決多數(shù)據(jù)庫(kù)連接的問(wèn)題往枷。零入侵并不是說(shuō)不需要寫(xiě)代碼蜂莉,而是在不改動(dòng)原代碼的基礎(chǔ)上實(shí)現(xiàn)需要的功能碎罚。
??本章所講的多數(shù)據(jù)源是采用ThreadLocal(線(xiàn)程變量)實(shí)現(xiàn)的会傲,在應(yīng)用需要做增刪改查的時(shí)候锅棕,會(huì)先獲取當(dāng)前線(xiàn)程的線(xiàn)程變量拙泽,根據(jù)獲取到的線(xiàn)程變量來(lái)選擇對(duì)應(yīng)的數(shù)據(jù)源。具體的執(zhí)行流程如下:

  1. 為線(xiàn)程A的ThreadLocal賦值
  2. 連接DynamicDataSource數(shù)據(jù)源
  3. DynamicDataSource數(shù)據(jù)源根據(jù)ThreadLocal所持有的值去選擇通過(guò)DataSource1還是DataSource2執(zhí)行該SQL語(yǔ)句

開(kāi)始構(gòu)造多數(shù)據(jù)源

由于多數(shù)據(jù)源需要配合ThreadLocal來(lái)實(shí)現(xiàn)裸燎,所以數(shù)據(jù)源的配置以java config的方式配置比較直觀(guān)顾瞻,如果之前是按照SpringBoot配置方式配置的可以更換成java config方式再來(lái)學(xué)習(xí)本章。

1.多數(shù)據(jù)源枚舉類(lèi)

由于demo比較簡(jiǎn)單德绿,所以直接使用1荷荤、2的方式命名,需要多少個(gè)數(shù)據(jù)源就添加多少個(gè)

public enum  DatabaseType {
    DATASOURCE1,DATASOURCE2
}
2. ThreadLocal

新建DatabaseContextHolder 類(lèi)移稳,該類(lèi)持有ThreadLocal對(duì)象

public class DatabaseContextHolder {
    private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>();
    public static void setDatabaseType(DatabaseType type) {
        contextHolder.set(type);
    }
    public static DatabaseType getDatabaseType() {
        return contextHolder.get();
    }
}

3蕴纳、Spring動(dòng)態(tài)數(shù)據(jù)源
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getDatabaseType();
    }
}
4.application.yml添加多數(shù)據(jù)源配置
datasource1.jdbc:
  driverClassName: com.mysql.jdbc.Driver
  jdbcUrl: jdbc:mysql://localhost:3306/technology-integration
  username: root
  password: 123456
  min-idle: 10

datasource2.jdbc:
  driverClassName: com.mysql.jdbc.Driver
  jdbcUrl: jdbc:mysql://localhost:3306/technology-integration2
  username: root
  password: 123456
  min-idle: 10
5.MybatisConfig配置

修改之前創(chuàng)建的MyBatisConfig類(lèi)


@Configuration
@MapperScan("com.viu.technology.mapper")
public class MybatisConfig extends HikariConfig{
    @Autowired
    private Environment env;

    //使用Hikaricp數(shù)據(jù)源
    @Bean("datasource1")
    @ConfigurationProperties(prefix = "datasource1.jdbc")
    public DataSource dataSource1() {
        log.info("開(kāi)始配置DataSource數(shù)據(jù)源1");
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName(env.getProperty("datasource1.jdbc.driverClassName"));
        dataSource.setJdbcUrl(env.getProperty("datasource1.jdbc.url"));
        dataSource.setUsername(env.getProperty("datasource1.jdbc.username"));
        dataSource.setPassword(env.getProperty("datasource1.jdbc.password"));
        dataSource.setMaximumPoolSize(100);
        dataSource.setPoolName("hikari-");
        return dataSource;
    }

    //配置第二個(gè)數(shù)據(jù)源
    @Bean("datasource2")
    @ConfigurationProperties(prefix = "datasource2.jdbc")
    public DataSource dataSource2() {
        log.info("開(kāi)始配置DataSource數(shù)據(jù)源2");
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName(env.getProperty("datasource2.jdbc.driverClassName"));
        dataSource.setJdbcUrl(env.getProperty("datasource2.jdbc.url"));
        dataSource.setUsername(env.getProperty("datasource2.jdbc.username"));
        dataSource.setPassword(env.getProperty("datasource2.jdbc.password"));
        return dataSource;
    }


    //配置多數(shù)據(jù)源,使用ThreadLocal對(duì)對(duì)應(yīng)的線(xiàn)程保存不用的類(lèi)型个粱,根據(jù)類(lèi)型獲取不同的數(shù)據(jù)源
    @Bean("datasource")
    @Primary
    public DynamicDataSource dataSource(@Qualifier("datasource1") DataSource dataSource1, 
@Qualifier("datasource2") DataSource dataSource2) {
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DatabaseType.DATASOURCE1, dataSource1);
        targetDataSource.put(DatabaseType.DATASOURCE2, dataSource2);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSource);
        dataSource.setDefaultTargetDataSource(dataSource1);
        return dataSource;
    }

    @Bean
    @Primary
    public SqlSessionFactory sqlSessionFactory( DataSource dataSource) throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(dataSource);
        fb.setTypeAliasesPackage("com.viu.technology.po");
        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapping/*.xml"));
        return fb.getObject();
    }
}



接下來(lái)主要解析一下AbstractRoutingDataSource 這個(gè)類(lèi)古毛,這個(gè)類(lèi)是Spring框架自帶的一個(gè)動(dòng)態(tài)切換數(shù)據(jù)源的類(lèi)。

首先需要看一下我們剛剛在MybatisConfig中注冊(cè)的DynamicDataSource這個(gè)Bean

溫馨提示:DynamicDataSource也是DataSource的實(shí)現(xiàn)類(lèi)

  1. 先是把兩個(gè)數(shù)據(jù)源添加到targetDataSource這個(gè)Map集合中
  2. 接著實(shí)例化出DynamicDataSource這個(gè)類(lèi)
  3. 設(shè)置dynamicDataSource的目標(biāo)數(shù)據(jù)源(也就是我們?cè)贛ybatisConfig中注冊(cè)的兩個(gè)數(shù)據(jù)源)
  4. 設(shè)置dynamicDataSource默認(rèn)使用的數(shù)據(jù)源都许,也就是沒(méi)有為T(mén)hreadLocal賦值的情況下默認(rèn)使用的的數(shù)據(jù)源
  5. 返回dynamicDataSource稻薇,完成DataSource Bean的注冊(cè)


    DynamicDataSource

注冊(cè)多數(shù)據(jù)源的整合主要就是在dynamicDataSource的實(shí)現(xiàn),接下來(lái)我們Ctrl+鼠標(biāo)左鍵進(jìn)入AbstractRoutingDataSource類(lèi)的源碼中


image.png
  • 可以看到AbstractRoutingDataSource里面的變量胶征,targetDataSources的值就是我們剛才在DynamicDataSource Bean中注入的目標(biāo)數(shù)據(jù)源塞椎。
  • defaultTargetDataSource則是我們剛才設(shè)置的默認(rèn)數(shù)據(jù)源



afterPropertiesSet()是AbstractRoutingDataSource的核心方法,我們則從這個(gè)方法開(kāi)始解析代碼

public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        } else {
            this.resolvedDataSources = new HashMap(this.targetDataSources.size());
            this.targetDataSources.forEach((key, value) -> {
                Object lookupKey = this.resolveSpecifiedLookupKey(key);
                DataSource dataSource = this.resolveSpecifiedDataSource(value);
                this.resolvedDataSources.put(lookupKey, dataSource);
            });
            if (this.defaultTargetDataSource != null) {
                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
            }
        }
    }

可以看到這句代碼弧烤,如果目標(biāo)數(shù)據(jù)源為空則會(huì)拋出IllegalArgumentException異常忱屑,而我們剛才已經(jīng)在MybatisConfig類(lèi)里面的dynamicDataSource()為其注入了兩個(gè)數(shù)據(jù)源

this.targetDataSources == null
Map<Object, Object> targetDataSource = new HashMap<>();
targetDataSource.put(DatabaseType.DATASOURCE1, dataSource1);
targetDataSource.put(DatabaseType.DATASOURCE2, dataSource2);
DynamicDataSource dataSource = new DynamicDataSource();

如果this.targetDataSources不為null,afterPropertiesSet()方法,則會(huì)將targetDataSource轉(zhuǎn)換為resolvedDataSources 暇昂。主要就是將targetDataSource中所有的value由Object對(duì)象轉(zhuǎn)換為DataSource對(duì)象莺戒,兩個(gè)map中的key存放的是我們自定義的DatabaseType 枚舉類(lèi)。這里的forEach使用的是lambda表達(dá)式急波,等同于foreach循環(huán)

private Map<Object, Object> targetDataSources;
private Map<Object, DataSource> resolvedDataSources;
 this.resolvedDataSources = new HashMap(this.targetDataSources.size());
            this.targetDataSources.forEach((key, value) -> {
                Object lookupKey = this.resolveSpecifiedLookupKey(key);
                DataSource dataSource = this.resolveSpecifiedDataSource(value);
                this.resolvedDataSources.put(lookupKey, dataSource);
            });

下面的代碼主要的操作使用resolveSpecifiedDataSource()方法从铲,將Object對(duì)象轉(zhuǎn)換為DataSource對(duì)象。如果Object對(duì)象為DataSource的實(shí)例澄暮,則直接強(qiáng)轉(zhuǎn)名段;如果Object對(duì)象為String的實(shí)例,則根據(jù)jndi的名字獲取DataSource泣懊;否則將拋出異常

protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
        if (dataSource instanceof DataSource) {
            return (DataSource)dataSource;
        } else if (dataSource instanceof String) {
            return this.dataSourceLookup.getDataSource((String)dataSource);
        } else {
            throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and 
String supported: " + dataSource);
        }
    }

最后AbstractRoutingDataSource會(huì)根據(jù)defaultTargetDataSource是否為空為resolvedDefaultDataSource賦值伸辟,resolvedDefaultDataSource是轉(zhuǎn)變后的DataSource默認(rèn)使用的數(shù)據(jù)源

if (this.defaultTargetDataSource != null) {
                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
            }

多數(shù)據(jù)源的存儲(chǔ)解析到此為止,接下來(lái)我們看看AbstractRoutingDataSource是如何切換數(shù)據(jù)源的馍刮。
這個(gè)方法則是AbstractRoutingDataSource切換數(shù)據(jù)源的關(guān)鍵信夫,讓我們來(lái)一步步解析該方法。

protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource 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 + "]");
        } else {
            return dataSource;
        }
    }

判斷resolvedDataSources是否為空,如果為空則拋出IllegalArgumentException異常

 Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");

這個(gè)方法為讀取resolvedDataSources的key静稻,用于獲取對(duì)應(yīng)的DataSource

Object lookupKey = this.determineCurrentLookupKey();

我們可以看到該類(lèi)下的方法為抽象方法警没,而我們繼承AbstractRoutingDataSource的DynamicDataSource類(lèi)則實(shí)現(xiàn)了這個(gè)抽象方法,方法返回了當(dāng)前線(xiàn)程所持有的線(xiàn)程變量,也就是DatabaseType其中的值振湾。

protected abstract Object determineCurrentLookupKey();
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getDatabaseType();
    }
}

determineTargetDataSource()根據(jù)實(shí)現(xiàn)類(lèi)重寫(xiě)的determineCurrentLookupKey()方法獲取到了當(dāng)前線(xiàn)程所持有的線(xiàn)程變量杀迹,根據(jù)獲取到的線(xiàn)程變量取出resolvedDataSources存儲(chǔ)的DataSource。

DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);

如果從resolvedDataSources獲取到的DataSource為空押搪,且當(dāng)前的線(xiàn)程變量為空树酪、lenientFallback 為true的情況下會(huì)使用resolvedDefaultDataSource中的DataSource進(jìn)行數(shù)據(jù)操作。lenientFallback 的意思是嵌言,當(dāng)datasource這個(gè)變量為空的時(shí)候會(huì)使用resolvedDefaultDataSource進(jìn)行數(shù)據(jù)源操作嗅回。

if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
       dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
      throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
 } else {
      return dataSource;
}

getConnection那些方法就不進(jìn)行解析了及穗,以上所有對(duì)源代碼的解讀均為個(gè)人理解珊膜,如有錯(cuò)誤歡迎指出窗骑。

Dao中切換數(shù)據(jù)源

只需要在執(zhí)行SQL語(yǔ)句前調(diào)用setDatabaseType方法為線(xiàn)程本地變量contextHolder賦值即可

/**
     * 使用數(shù)據(jù)源2
     * @param id
     * @return
     */
    public User selectUserById(String id) {
        DatabaseContextHolder.setDatabaseType(DatabaseType.DATASOURCE2);
        return userMapper.selectByPrimaryKey(id);
    }


更多文章請(qǐng)關(guān)注該 technology-integration全面解析專(zhuān)題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子商模,更是在濱河造成了極大的恐慌,老刑警劉巖订框,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件链嘀,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鹃栽,警方通過(guò)查閱死者的電腦和手機(jī)躏率,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)民鼓,“玉大人薇芝,你說(shuō)我怎么就攤上這事》峒危” “怎么了夯到?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)饮亏。 經(jīng)常有香客問(wèn)我耍贾,道長(zhǎng),這世上最難降的妖魔是什么路幸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任荐开,我火速辦了婚禮,結(jié)果婚禮上简肴,老公的妹妹穿的比我還像新娘晃听。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布杂伟。 她就那樣靜靜地躺著移层,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赫粥。 梳的紋絲不亂的頭發(fā)上观话,一...
    開(kāi)封第一講書(shū)人閱讀 49,806評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音越平,去河邊找鬼频蛔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛秦叛,可吹牛的內(nèi)容都是我干的晦溪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼挣跋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼三圆!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起避咆,我...
    開(kāi)封第一講書(shū)人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舟肉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后查库,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體路媚,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年樊销,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了整慎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡围苫,死狀恐怖裤园,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情够吩,我是刑警寧澤比然,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站周循,受9級(jí)特大地震影響强法,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜湾笛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一饮怯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嚎研,春花似錦蓖墅、人聲如沸库倘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)教翩。三九已至,卻和暖如春贪壳,著一層夾襖步出監(jiān)牢的瞬間饱亿,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工闰靴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留彪笼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓蚂且,卻偏偏與公主長(zhǎng)得像配猫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杏死,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

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

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,338評(píng)論 8 265
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理泵肄,服務(wù)發(fā)現(xiàn),斷路器识埋,智...
    卡卡羅2017閱讀 134,633評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法凡伊,類(lèi)相關(guān)的語(yǔ)法零渐,內(nèi)部類(lèi)的語(yǔ)法窒舟,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法诵盼,線(xiàn)程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,598評(píng)論 18 399
  • Difference is what you want to be and what you have to be...
    江山十里閱讀 372評(píng)論 0 0
  • 生活在互聯(lián)網(wǎng)時(shí)代惠豺,尤其是對(duì)于電商或與電腦等重度使用相關(guān)工作者,一臺(tái)手機(jī)风宁、一臺(tái)電腦即是一個(gè)辦公工具洁墙。打造高效率的辦公...
    黃小忠閱讀 474評(píng)論 1 0