本篇文章将向大家介紹 MyBatis 内置資料源的實作邏輯。搞懂這些資料源的實作,可使大家對資料源有更深入的認識。同時在配置這些資料源時,也會更清楚每種屬性的意義和用途。是以,如果大家想知其然,也知其是以然。那麼接下來就讓我們一起去探索 MyBatis 内置資料源的源碼吧。
MyBatis 支援三種資料源配置,分别為 UNPOOLED、POOLED 和 JNDI。并提供了兩種資料源實作,分别是 UnpooledDataSource 和 PooledDataSource。在三種資料源配置中,UNPOOLED 和 POOLED 是我們最常用的兩種配置。至于 JNDI,MyBatis 提供這種資料源的目的是為了讓其能夠運作在 EJB 或應用伺服器等容器中,這一點官方文檔中有所說明。由于 JNDI 資料源在日常開發中使用甚少,是以,本篇文章不打算分析 JNDI 資料源相關實作。大家若有興趣,可自行分析。接下來,本文将重點分析 UNPOOLED 和 POOLED 兩種資料源。其他的就不多說了,進入正題吧。
在詳細分析 UnpooledDataSource 和 PooledDataSource 兩種資料源實作之前,我們先來了解一下資料源的配置與初始化過程。現在看資料源是如何配置的,如下:
資料源的配置是内嵌在 <environment> 節點中的,MyBatis 在解析 <environment> 節點時,會一并解析資料源的配置。MyBatis 會根據具體的配置資訊,為不同的資料源建立相應工廠類,通過工廠類即可建立資料源執行個體。關于資料源配置的解析以及資料源工廠類的建立過程,我在 MyBatis 配置檔案解析過程一文中分析過,這裡就不贅述了。下面我們來看一下資料源工廠類的實作邏輯。
以上是 UnpooledDataSourceFactory 的源碼分析,除了 setProperties 方法稍複雜一點,其他的都比較簡單,就不多說了。下面看看 PooledDataSourceFactory 的源碼。
以上就是 PooledDataSource 類的所有源碼,PooledDataSourceFactory 繼承自 UnpooledDataSourceFactory,複用了父類的邏輯,是以它的實作很簡單。
關于兩種資料源的建立過程就先分析到這,接下來,我們去探究一下兩種資料源是怎樣實作的。
UnpooledDataSource,從名稱上即可知道,該種資料源不具有池化特性。該種資料源每次會傳回一個新的資料庫連接配接,而非複用舊的連接配接。由于 UnpooledDataSource 無需提供連接配接池功能,是以它的實作非常簡單。核心的方法有三個,分别如下:
initializeDriver - 初始化資料庫驅動
doGetConnection - 擷取資料連接配接
configureConnection - 配置資料庫連接配接
下面我将按照順序分節對相關方法進行分析,由于 configureConnection 方法比較簡單,是以我把它和 doGetConnection 放在一節中進行分析。下面先來分析 initializeDriver 方法。
回顧我們一開始學習使用 JDBC 通路資料庫時的情景,在執行 SQL 之前,通常都是先擷取資料庫連接配接。一般步驟都是加載資料庫驅動,然後通過 DriverManager 擷取資料庫連接配接。UnpooledDataSource 也是使用 JDBC 通路資料庫的,是以它擷取資料庫連接配接的過程也大緻如此,隻不過會稍有不同。下面我們一起來看一下。
如上,initializeDriver 方法主要包含三步操作,分别如下:
加載驅動
通過反射建立驅動執行個體
注冊驅動執行個體
這三步都是都是正常操作,比較容易了解。上面代碼中出現了緩存相關的邏輯,這個是用于避免重複注冊驅動。因為 initializeDriver 方法并不是在 UnpooledDataSource 初始化時被調用的,而是在擷取資料庫連接配接時被調用的。是以這裡需要做個檢測,避免每次擷取資料庫連接配接時都重新注冊驅動。這個是一個比較小的點,大家看代碼時注意一下即可。下面看一下擷取資料庫連接配接的邏輯。
在使用 JDBC 時,我們都是通過 DriverManager 的接口方法擷取資料庫連接配接。本節所要分析的源碼也不例外,一起看一下吧。
如上,上面方法将一些配置資訊放入到 Properties 對象中,然後将資料庫連接配接和 Properties 對象傳給 DriverManager 的 getConnection 方法即可擷取到資料庫連接配接。
好了,關于 UnpooledDataSource 就先說到這。下面分析一下 PooledDataSource,它的實作要複雜一些。
PooledDataSource 内部實作了連接配接池功能,用于複用資料庫連接配接。是以,從效率上來說,PooledDataSource 要高于 UnpooledDataSource。PooledDataSource 需要借助一些輔助類幫助它完成連接配接池的功能,是以接下來,我們先來認識一下相關的輔助類。
PooledDataSource 需要借助兩個輔助類幫其完成功能,這兩個輔助類分别是 PoolState 和 PooledConnection。PoolState 用于記錄連接配接池運作時的狀态,比如連接配接擷取次數,無效連接配接數量等。同時 PoolState 内部定義了兩個 PooledConnection 集合,用于存儲空閑連接配接和活躍連接配接。PooledConnection 内部定義了一個 Connection 類型的變量,用于指向真實的資料庫連接配接。以及一個 Connection 的代理類,用于對部分方法調用進行攔截。至于為什麼要攔截,随後将進行分析。除此之外,PooledConnection 内部也定義了一些字段,用于記錄資料庫連接配接的一些運作時狀态。接下來,我們來看一下 PooledConnection 的定義。
下面再來看看 PoolState 的定義。
上面對 PooledConnection 和 PoolState 的定義進行了一些注釋,這兩個類中有很多字段用來記錄運作時狀态。但在這些字段并非核心,是以大家知道每個字段的用途就行了。關于這兩個輔助類的介紹就先到這
前面已經說過,PooledDataSource 會将用過的連接配接進行回收,以便可以複用連接配接。是以從 PooledDataSource 擷取連接配接時,如果空閑連結清單裡有連接配接時,可直接取用。那如果沒有空閑連接配接怎麼辦呢?此時有兩種解決辦法,要麼建立新連接配接,要麼等待其他連接配接完成任務。具體怎麼做,需視情況而定。下面我們深入到源碼中一探究竟。
上面代碼冗長,過程比較複雜,下面把代碼邏輯梳理一下。從連接配接池中擷取連接配接首先會遇到兩種情況:
連接配接池中有空閑連接配接
連接配接池中無空閑連接配接
對于第一種情況,處理措施就很簡單了,把連接配接取出傳回即可。對于第二種情況,則要進行細分,會有如下的情況。
活躍連接配接數沒有超出最大活躍連接配接數
活躍連接配接數超出最大活躍連接配接數
對于上面兩種情況,第一種情況比較好處理,直接建立新的連接配接即可。至于第二種情況,需要再次進行細分。
活躍連接配接的運作時間超出限制,即逾時了
活躍連接配接未逾時
對于第一種情況,我們直接将逾時連接配接強行中斷,并進行復原,然後複用部分字段重新建立 PooledConnection 即可。對于第二種情況,目前沒有更好的處理方式了,隻能等待了。下面用一段僞代碼示範各種情況及相應的處理措施,如下:
最後用一個流程圖大緻描繪 popConnection 的邏輯,如下:

相比于擷取連接配接,回收連接配接的邏輯要簡單的多。回收連接配接成功與否隻取決于空閑連接配接集合的狀态,所需處理情況很少,是以比較簡單。下面看一下相關的邏輯。
上面代碼首先将連接配接從活躍連接配接集合中移除,然後再根據空閑集合是否有空閑空間進行後續處理。如果空閑集合未滿,此時複用原連接配接的字段資訊建立新的連接配接,并将其放入空閑集合中即可。若空閑集合已滿,此時無需回收連接配接,直接關閉即可。pushConnection 方法的邏輯并不複雜,就不多說了。
我們知道擷取連接配接的方法 popConnection 是由 getConnection 方法調用的,那回收連接配接的方法 pushConnection 是由誰調用的呢?答案是 PooledConnection 中的代理邏輯。相關代碼如下:
在上一節中,getConnection 方法傳回的是 Connection 代理對象,不知道大家有沒有注意到。代理對象中的方法被調用時,會被上面的代理邏輯所攔截。如果代理對象的 close 方法被調用,MyBatis 并不會直接調用真實連接配接的 close 方法關閉連接配接,而是調用 pushConnection 方法回收連接配接。同時會喚醒處于睡眠中的線程,使其恢複運作。整個過程并不複雜,就不多說了。
本章分析了 PooledDataSource 的部分源碼及一些輔助類的源碼,除此之外,PooledDataSource 中還有部分源碼沒有分析,大家若有興趣,可自行分析。好了,關于 PooledDataSource 的分析就先到這。
本篇文章對 MyBatis 兩種内置資料源進行了較為詳細的分析,總的來說,這兩種資料源的源碼都不是很難了解。大家在閱讀源碼的過程中,首先應搞懂源碼的主要邏輯,然後再去分析一些邊邊角角的邏輯。不要一開始就陷入各種細節中,容易迷失方向。
好了,到此本文就結束了。若文章有錯誤不妥之處,希望大家指明。最後,感謝大家閱讀我的文章。
更新時間
标題
2018-09-11
MyBatis 源碼分析系列文章合集
2018-07-16
MyBatis 源碼分析系列文章導讀
2018-07-20
MyBatis 源碼分析 - 配置檔案解析過程
2018-07-30
MyBatis 源碼分析 - 映射檔案解析過程
2018-08-17
MyBatis 源碼分析 - SQL 的執行過程
2018-08-19
MyBatis 源碼分析 - 内置資料源
2018-08-25
MyBatis 源碼分析 - 緩存原理
2018-08-26
MyBatis 源碼分析 - 插件機制
本文在知識共享許可協定 4.0 下釋出,轉載需在明顯位置處注明出處 作者:田小波 本文同步釋出在我的個人部落格:http://www.tianxiaobo.com
本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協定進行許可。