天天看點

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

常見的資料源元件都實作了javax.sql.DataSource接口;

MyBatis不但要能內建第三方的資料源元件,自身也提供了資料源的實作;

一般情況下,資料源的初始化過程參數較多,比較複雜;

(采用工廠模式)

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

其實mybatis采用工廠模式,也是為了能适應更多的第三方資料源元件,如果有新的元件進來,隻需要寫類實作就行,工廠模式的優點很多,比如Spring的ioc,就是把對象的建立和對象的使用分離開來,解耦,new關鍵字耦合太多,對象的建立和管理由spring來做,而對象的使用是開發人員自己。

資料源子產品的類圖:

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

看一下DataSourceFactory接口:

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

這個接口的實作類有:

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

DataSourceFactory的實作類主要看 UnpooledDataSourceFactory這個類,也是一個工廠類。是一個不用連接配接池的資料連結工廠。

為什麼說資料源很複雜,因為屬性太多了,一般配置驅動。資料庫url。使用者名,密碼等等實際上還可以配置很多

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

來看屬性DataSource,這個也是一個接口

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

看DataSource接口的實作類PoolDataSource,使用連接配接池的資料源。

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

這個類還有很多初始化的構造函數,全是重載的。

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

是以才使用工廠來建立DataSource的對象/

再來回到這個圖,根據代碼也可以看到PooledDataSourceFactory繼承了UnPooledDataSourceFactory

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

注意把邏輯厘清楚:字尾帶Factory的都是工廠類,不過有的是Unpool,有的是pool,而且pool的工廠類繼承Unpool的工廠類,而Unpool的工廠類是用來生産Unpool的資料源,pool的工廠類是用來生産pool的資料源,看下圖由虛線箭頭的creat标志。

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

看UnpooledDataSource這個類,implements DataSource接口

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

這個問題來Debug一下:寫一個Demo類,打斷點

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

在UnpooledDataSource類的static的代碼塊中打上斷點:

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

在DriverManager類的static的代碼塊中打上斷點:

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

之後斷點一直進入到上圖LoadInitialDrivers();

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

然後進入這個load方法,打上斷點

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

因為參數是Driver.class,是以ServiceLoader的常量service = Driver.class

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

那麼來看構造方法

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

可以看到調用reload(),裡面就是調用了new LazyIterator(service,loader),這是ServiceLoader類的内部類。

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

然後跳過,一直回到

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

在這個while循環裡面跟進去, (上圖是跟完以後的,下圖是具體跟的步驟)

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

PREFIX 和 service 是什麼 看下圖:

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

然後 就去 這個目錄下

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

parse解析

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

傳回true ,然會while循環才進去

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

跟進next()

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

傳回true,繼續

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

就是在這塊進行的。Class.forName,

然後CopyOnWritelist裡面就儲存了mysql驅動

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

這塊ServiceLoader.load()屬于Java SPI的知識,其本質就是服務的提供者,提供了服務接口的一種實作之後,在jar包的META-INF/services/目錄裡同時建立一個以服務接口命名的檔案。該檔案裡就是實作該服務接口的具體實作類。而當外部程式裝配這個子產品的時候,就能通過該jar包META-INF/services/裡的配置檔案找到具體的實作類名,并裝載執行個體化,完成子產品的注入。 java.util.ServiceLoader工具類方法會使用ClassLoad類的getResources方法擷取指定目錄下的檔案,讀取檔案内容并解析擷取所有接口實作類的全限路徑,根據全限路徑使用class.forName裝載class檔案到JVM,然後通過c.newInstance()執行個體化類轉化接口類型并放入providers緩存提供者集合中,以供後面使用。上面這段話是摘自

JavaSPI,ServiceLoader.load方法詳解_hupoling的專欄-CSDN部落格_serviceloader.load​blog.csdn.net

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

有興趣的去看看,我的源碼解讀也很清楚

注意,這是mybatis的加載方式,jdbc的方式調用鍊還不太一樣。可以寫一個測試類用Class.forName()去debug看一下。

unpooledDatasource就是不用連接配接池的資料源,每次都是建立新的資料庫連接配接。而pooledDatasource則不一樣,會去從池子裡找。

接下來看一下UnpooledDatasource是怎麼産生資料庫連接配接的

當 <dataSource>的type屬性被配置成了”UNPOOLED”,MyBatis首先會執行個體化一個UnpooledDataSourceFactory工廠執行個體,然後通過.getDataSource()方法傳回一個UnpooledDataSource執行個體對象引用,使用UnpooledDataSource的getConnection(),每調用一次就會産生一個新的Connection執行個體對象。

看一下:UnpooledDataSource的getConnection()

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

1.

初始化驅動:

判斷driver驅動是否已經加載到記憶體中,如果還沒有加載,則會動态地加載driver類,并執行個體化一個Driver對象,使用DriverManager.registerDriver()方法将其注冊到記憶體中,以供後續使用。

2.

建立Connection對象:

使用DriverManager.getConnection()方法建立連接配接。

3.

配置Connection對象:

設定是否自動送出autoCommit和隔離級别isolationLevel。

4. 傳回Connection對象。

看上圖方法的第一行代碼: initializeDriver(),畫紅框的不用多說了。

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

接着第二行代碼:

Connection connection = DriverManager.getConnection(

url

, properties);

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
每次都要生成新的connection, 開銷很大,這就是沒有連接配接池的資料源

下來看有連接配接池的資料源 PooledDataSource 一個簡單,同步的、線程安全的資料庫連接配接池,基于PooledConnection和PoolState

有幾個對象需要知道:

1、PooledConnection:

使用動态代理封裝了真正的資料庫連接配接對象Connection;

可以看到 PooledConnection 實作了 invocationhandler,動态代理。

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

真正的資料庫連接配接對象是 Connection,作用是:連接配接資料庫,擷取查詢資料,更新資料

那麼 PooledConnection 是想對Connection進行增強。直接看invoke方法

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品
2、PoolState:

用于管理PooledConnection對象狀态的元件,通過兩個list分别 管理空閑狀态的連接配接資源和活躍狀态的連接配接資源

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

千萬記住這兩個ArrayList,以及ArrayList的remove方法的使用.

getConnection()擷取連接配接的流程圖

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

看一下擷取有連接配接池的資料源 -> 擷取 -> 資料庫連接配接的代碼:

PooledDataSource.getConnection()

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

擷取連接配接就是在這個紅框的 popConnection方法中做的。代碼很長,邏輯不難,if - else判斷比較多,和流程圖一樣,完全可以看懂,就不做分析了。

popConnection的代碼:

一個資料表連接配接兩個資料源_Mybatis源碼分析(二): 資料源子產品

注意這個資料結構,list是有序的集合,而remove(0)就是移除第一個并且傳回。剩下的就不再分析了,代碼很簡單。關閉連接配接也有相應的代碼,具體就不做分析了。