目錄
1. 介紹
2. 基本原理
3. 源碼介紹
3.1 使用 AOP 攔截,方法執(zhí)行前獲取到當(dāng)前方法要用的數(shù)據(jù)源
3.2 實(shí)現(xiàn)自定義?DataSource 接口,實(shí)現(xiàn) DataSource 接口的 getConnect 方法做動(dòng)態(tài)處理
1. 介紹
多數(shù)據(jù)源即一個(gè)項(xiàng)目中同時(shí)存在多個(gè)不同的數(shù)據(jù)庫(kù)連接池。
比如 127.0.0.1:3306/test? ?127.0.0.1:3307/test?127.0.0.1:3308/test
總之項(xiàng)目存在需要操作多個(gè)庫(kù)的需求。
具體在編碼方面呢,具體就是一個(gè)service 中,方法1使用庫(kù)1查詢,方法2使用庫(kù)2查詢。
2. 基本原理
多數(shù)據(jù)源實(shí)現(xiàn)原理是什么呢?可分為兩大關(guān)鍵部分
1. 使用 AOP 攔截,方法執(zhí)行前獲取到當(dāng)前方法要用的數(shù)據(jù)源
可以使用自定義注解實(shí)現(xiàn),注解參數(shù)帶數(shù)據(jù)源名稱,然后自己解析
2. 實(shí)現(xiàn)自定義?DataSource 接口,實(shí)現(xiàn) DataSource 接口的 getConnect 方法做動(dòng)態(tài)處理
動(dòng)態(tài)處理,就是拿到 AOP 那一步獲取到的數(shù)據(jù)源,直接返回該數(shù)據(jù)源
基本原理,可看這個(gè)簡(jiǎn)易圖
3. 源碼介紹
源碼地址?https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter
(源碼一定要自己完整看一遍,此篇博客只展示部分關(guān)鍵源碼)
3.1 使用 AOP 攔截,方法執(zhí)行前獲取到當(dāng)前方法要用的數(shù)據(jù)源
@DS 注解代表定義當(dāng)前方法、當(dāng)前類使用哪個(gè)數(shù)據(jù)源
?value 指定當(dāng)前類、方法使用的數(shù)據(jù)源名稱
數(shù)據(jù)源名稱也是在配置文件中定義的?
注解處理切面?DynamicDataSourceAnnotationAdvisor
切面 advice 由外部傳過(guò)來(lái),要處理的注解也從外面?zhèn)鬟^(guò)來(lái)。
也就是這里,這行代碼的意思是
DynamicDataSourceAnnotationInterceptor 負(fù)責(zé)處理?DS 注解
?接著看?DynamicDataSourceAnnotationInterceptor 如何處理
下面分別解釋下
1. 將 @DS 注解的 value 值壓入 ThreadLocal 當(dāng)前線程的棧
看這段方法 DynamicDataSourceContextHolder.push(dsKey);
ThreadLocal 中存儲(chǔ)的是?Deque 類,也就是一個(gè)雙端隊(duì)列(兩頭都可以插入的隊(duì)列)?,使用的是?ArrayDeque 雙端隊(duì)列,內(nèi)部是一個(gè)數(shù)組。
為什么使用隊(duì)列,而不是簡(jiǎn)單一個(gè)字符串,注釋已經(jīng)寫的很清楚了,看注釋即可。
?ArrayDeque 的 push 就是在隊(duì)列首部添加一個(gè)元素。
2. 調(diào)用實(shí)際的方法
這里不是切面嗎,實(shí)際方法也就是被攔截的方法。也就是直接調(diào)用業(yè)務(wù)邏輯。
3. 將第 1 步中的元素彈出來(lái)
業(yè)務(wù)邏輯執(zhí)行完成后,就將剛才加入的元素彈出來(lái)。
其實(shí)這里很像 JVM 虛擬機(jī)棧,方法調(diào)用就是壓入棧,方法結(jié)束調(diào)用就是出棧,棧頂就是當(dāng)前執(zhí)行的方法。
而此處這里的棧頂就代表 當(dāng)前正在執(zhí)行的方法所用的 數(shù)據(jù)源名稱。而方法執(zhí)行完了,這里也該出棧了。
這里核心邏輯已經(jīng)完了,本質(zhì)就是這么簡(jiǎn)單。
3.2 實(shí)現(xiàn)自定義?DataSource 接口,實(shí)現(xiàn) DataSource 接口的 getConnect 方法做動(dòng)態(tài)處理
DynamicRoutingDataSource 代表動(dòng)態(tài)路由數(shù)據(jù)源
做的事情就是運(yùn)行時(shí)動(dòng)態(tài)路由出一個(gè)當(dāng)前需要的數(shù)據(jù)源。
接著看源碼,首先與 SpringBoot 整合時(shí) 自動(dòng)配置出當(dāng)前 Bean
DynamicRoutingDataSource 類圖
3.2.1 DataSource
代表一個(gè)數(shù)據(jù)源,由javax 擴(kuò)展定義
3.2.2?AbstractDataSource
抽象實(shí)現(xiàn),將一些對(duì)數(shù)據(jù)源的配置操作都實(shí)現(xiàn)為不支持操作拋出異常 UnsupportedOperationException
(動(dòng)態(tài)數(shù)據(jù)源相當(dāng)于一個(gè)代理,不需要給動(dòng)態(tài)數(shù)據(jù)源本身設(shè)置相關(guān)配置)
3.2.3 AbstractRoutingDataSource
抽象實(shí)現(xiàn),路由動(dòng)態(tài)配置源,實(shí)現(xiàn)了關(guān)鍵方法?getConnection ,完成了路由操作
看看源碼??getConnection()
getConnection() 何時(shí)調(diào)用呢,也就是上一步的切面中的第 2 步中,invocation.proceed(),執(zhí)行業(yè)務(wù)邏輯的過(guò)程中,遇到的 數(shù)據(jù)庫(kù)層的操作時(shí),就會(huì)到這里了。
這里直接看簡(jiǎn)單的非事務(wù)的獲取數(shù)據(jù)源這里。
關(guān)鍵代碼 determineDataSource().getConnection()
這個(gè)方法由子類實(shí)現(xiàn),也就是下面的?DynamicRoutingDataSource 類
3.2.4?DynamicRoutingDataSource
動(dòng)態(tài)路由數(shù)據(jù)源核心實(shí)現(xiàn),完成?數(shù)據(jù)源的維護(hù)(添加刪除數(shù)據(jù)源)、數(shù)據(jù)源的選擇
接著上面的源碼流程,子類的 determineDataSource 方法最終調(diào)用了?getDataSource
?getDataSource 源碼如下
/**
* 獲取數(shù)據(jù)源
*
* @param ds 數(shù)據(jù)源名稱
* @return 數(shù)據(jù)源
*/
public DataSource getDataSource(String ds) {
if (StringUtils.isEmpty(ds)) {
// 沒(méi)有指定數(shù)據(jù)源名稱,直接使用默認(rèn)的數(shù)據(jù)源
return determinePrimaryDataSource();
} else if (!groupDataSources.isEmpty() && groupDataSources.containsKey(ds)) {
// 從分組數(shù)據(jù)源中找一個(gè)數(shù)據(jù)源
return groupDataSources.get(ds).determineDataSource();
} else if (dataSourceMap.containsKey(ds)) {
// 直接根據(jù)名稱找一個(gè)數(shù)據(jù)源
return dataSourceMap.get(ds);
}
if (strict) {
// 開(kāi)啟了嚴(yán)格模式時(shí),如果沒(méi)有找到數(shù)據(jù)源,就拋出異常
throw new CannotFindDataSourceException("dynamic-datasource could not find a datasource named" + ds);
}
// 使用默認(rèn)的數(shù)據(jù)源
return determinePrimaryDataSource();
}
下面分別講解關(guān)鍵之處
1.?沒(méi)有指定數(shù)據(jù)源名稱,直接使用默認(rèn)的數(shù)據(jù)源
沒(méi)有指定代表的是沒(méi)有加 @DS 注解,或者加了注解,但是 value 值沒(méi)有寫
此時(shí)就是用默認(rèn)的數(shù)據(jù)源,默認(rèn)的數(shù)據(jù)源是什么呢?
也就是配置文件中的 primary 中指定的數(shù)據(jù)源名稱,如果不配置的話默認(rèn)值就是 master
2.?實(shí)現(xiàn)自定義?DataSource 接口,實(shí)現(xiàn) DataSource 接口的 getConnect 方法做動(dòng)態(tài)處理
從分組找一個(gè)數(shù)據(jù)源 groupDataSources.get(ds).determineDataSource();
分組是什么意思?
分組定義的規(guī)則是 group_xxx,也就是數(shù)據(jù)源名稱以下劃線分割,下劃線前面的就是組名。
分組的作用是什么呢?本質(zhì)用于實(shí)現(xiàn)一個(gè)名稱對(duì)應(yīng)多數(shù)據(jù)庫(kù)源。
比如一主多從,可以將從數(shù)據(jù)源都分到 slave 組里面,用的時(shí)候就是 @DS("slave") // 組名
在實(shí)際決定數(shù)據(jù)源的時(shí)候,就會(huì)按照一定的策略從這個(gè)組里的數(shù)據(jù)源挑選一個(gè)了。
接著看源碼,如何?從分組數(shù)據(jù)源中找一個(gè)數(shù)據(jù)源
groupDataSources.get(ds).determineDataSource();
?最后到了策略的選擇,DynamicDataSourceStrategy
DynamicDataSourceStrategy 有兩個(gè)實(shí)現(xiàn)類
LoadBalanceDynamicDataSourceStrategy 負(fù)載均衡動(dòng)態(tài)數(shù)據(jù)源策略
看源碼,這個(gè)就是按順序一個(gè)個(gè)選擇下來(lái),達(dá)到負(fù)載均衡方式
RandomDynamicDataSourceStrategy 隨機(jī)動(dòng)態(tài)數(shù)據(jù)源策略
這個(gè)完全就是純隨機(jī)選一個(gè)
3.?直接根據(jù)名稱找一個(gè)數(shù)據(jù)源?
如果走到了這里,說(shuō)明這個(gè)數(shù)據(jù)源名稱沒(méi)有配置分組,那就直接根據(jù)名稱取這單個(gè)數(shù)據(jù)源了
直接純 get 了
數(shù)據(jù)源何時(shí)初始化的
還是在?DynamicRoutingDataSource,這個(gè)類實(shí)現(xiàn)了 Spring?InitializingBean?
接口回調(diào)方法?afterPropertiesSet,當(dāng)當(dāng)前 Bean 內(nèi)部的屬性都初始化完畢了后就回調(diào)這個(gè)方法
?看看??afterPropertiesSet 回調(diào)方法內(nèi)容
?這里只看關(guān)鍵代碼
1.?dataSources.putAll(provider.loadDataSources());
@Autowired private List<DynamicDataSourceProvider> providers;?providers 是什么呢???
providers 代表 動(dòng)態(tài)數(shù)據(jù)源配置的來(lái)源,默認(rèn)實(shí)現(xiàn)就是從 yml 中來(lái),也就是 SpringBoot 的 application.yml 配置
默認(rèn)實(shí)現(xiàn)
傳進(jìn)去的參數(shù)配置類
DynamicDataSourceProvider?也就是解析了這些配置 來(lái)獲取到所有配置
拿到配置后,就要解析這些配置了 ,這里委托了父類處理
這里完成創(chuàng)建數(shù)據(jù)源,然后將結(jié)果封裝成了?Map<String, DataSource> dataSourceMap 返回
(泛型為 <數(shù)據(jù)源名稱,數(shù)據(jù)源實(shí)例>)
?看看如何創(chuàng)建數(shù)據(jù)源的?defaultDataSourceCreator.createDataSource(dataSourceProperty)
大致流程如下:
這里介紹一下?creators
dynamic-datasource-creator 模塊下定義了單獨(dú)數(shù)據(jù)源創(chuàng)建的代碼
DataSourceCreator 代表一個(gè)數(shù)據(jù)源創(chuàng)建器,用于創(chuàng)建一個(gè)數(shù)據(jù)源。
?每種數(shù)據(jù)源類型都有自己的創(chuàng)建器,比如這里常見(jiàn)的?Druid、Hikar
這里就舉例其中一個(gè)?HikariDataSourceCreator,其他的都差不多
HikariDataSourceCreator
調(diào)用這些創(chuàng)造器的創(chuàng)建的時(shí)候默認(rèn)直接就啟動(dòng)了,除非配置了懶加載。
到現(xiàn)在,數(shù)據(jù)源就已經(jīng)創(chuàng)建完了。再次說(shuō)一下這是在Spring 的?afterPropertiesSet 回調(diào)里完成創(chuàng)建的。(afterPropertiesSet? 即當(dāng)前 Bean 的所有屬性 Spring 都填充完畢后回調(diào))
2.?addDataSource(dsItem.getKey(), dsItem.getValue());
上一步的?provider.loadDataSources()?講解完畢了,這次看看下面的?addDataSource(dsItem.getKey(), dsItem.getValue());
?addDataSource 方法
?首先是先給?dataSourceMap 放進(jìn)去了。這里會(huì)返回舊的數(shù)據(jù)源(如果是第一次加入,則返回null),所以下面判斷了如果返回有值舊關(guān)閉掉舊的數(shù)據(jù)源,關(guān)閉就是調(diào)用數(shù)據(jù)源的 close 方法。
然后是 addGroupDataSource
這里數(shù)據(jù)源就完成了添加,這個(gè)整體步驟都是在啟動(dòng)的時(shí)候添加的, 后面的 getConnect 方法都只是獲取了。?
最后再放這張圖簡(jiǎn)單總結(jié)下。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-733346.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-733346.html
到了這里,關(guān)于MyBatis Plus 插件 動(dòng)態(tài)數(shù)據(jù)源實(shí)現(xiàn)原理與源碼講解 (dynamic-datasource-spring-boot-starter-master)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!