天天看點

C3P0資料連接配接池源碼-擷取連接配接對象的過程

作者:老吾頻道

源碼分析

C3P0是一個功能強大的資料庫連接配接池庫,但是它的源碼确實令人望而生畏。沒有注釋和奇特的代碼格式使得閱讀起來非常困難。是以,當我剛開始接觸C3P0時,我并沒有深入研究它的源碼,因為我覺得這是一項艱巨的任務。

然而,現在我決定再次仔細閱讀C3P0的源碼,盡管需要花費相當多的時間。在這次源碼分析中,我将重點關注類與類之間的關系以及一些重要功能的實作,而不像以前那樣一步步地進行探索。

此外,C3P0廣泛使用了監聽器和多線程技術,它們是Java開發中的常見功能,是以本文不會深入探讨其原理。如果你對這些内容感興趣,我建議你進一步學習,因為在實際項目中你也可能會用到它們。

通過這次源碼分析,我希望能夠更好地了解C3P0的内部工作原理,提升對連接配接池的了解,并且能夠更好地使用和調優C3P0來滿足實際項目的需求。

建立資料源對象

我們使用c3p0時,一般會以ComboPooledDataSource這個類為入口,那麼就從這個類展開吧。首先,看下ComboPooledDataSource的UML圖。(圖檔來源于網絡)

C3P0資料連接配接池源碼-擷取連接配接對象的過程

下面重點說下幾個類的作用:

類名 描述
DataSource 用于建立原生的Connection
ConnectionPoolDataSource 用于建立PooledConnection
PooledDataSource 用于支援對c3p0連接配接池中連接配接數量和狀态等的監控
IdentityTokenized 用于支援注冊功能。每個DataSource執行個體都有一個identityToken,用于在C3P0Registry中注冊
PoolBackedDataSourceBase 實作了IdentityTokenized接口,還持有PropertyChangeSupport和VetoableChangeSupport對象,并提供了添加和移除監聽器的方法
AbstractPoolBackedDataSource 實作了PooledDataSource和DataSource
AbstractComboPooledDataSource 提供了資料源參數配置的setter/getter方法
DriverManagerDataSource DataSource實作類,用于建立原生的Connection
WrapperConnectionPoolDataSource ConnectionPoolDataSource實作類,用于建立PooledConnection
C3P0PooledConnectionPoolManager 連接配接池管理器,非常重要。用于建立連接配接池,并持有連接配接池的Map(根據賬号密碼比對連接配接池)。

new一個ComboPooledDataSource對象時,主要做了幾件事:

  1. 獲得this的identityToken,并注冊到C3P0Registry
  2. 添加監聽配置參數改變的Listenner
  3. 建立DriverManagerDataSource和WrapperConnectionPoolDataSource對象

(此處已添加書籍卡片,請到今日頭條用戶端檢視)

獲得this的identityToken,并注冊到C3P0Registry

在C3P0中,身份令牌是為了區分不同的連接配接池而引入的。每個連接配接池都有一個唯一的身份令牌,用于辨別該連接配接池。通過身份令牌,C3P0能夠準确地定位和管理連接配接池。

allocateIdentityToken方法的作用是配置設定一個新的身份令牌。它確定每個連接配接池都具有唯一的身份令牌,以避免沖突和混淆。

在該方法的實作中,C3P0使用了一種基于計數器的算法來生成身份令牌。每次調用allocateIdentityToken方法時,計數器會遞增,并将遞增後的值作為新的身份令牌。這樣可以確定每個連接配接池都有一個不同的身份令牌。

通過配置設定唯一的身份令牌,C3P0能夠正确地管理多個連接配接池,并確定它們之間的隔離性和準确性。

C3P0資料連接配接池源碼-擷取連接配接對象的過程

allocateIdentityToken

接下來,再來看下注冊過程,調用的是C3P0Registry的incorporate方法。

C3P0資料連接配接池源碼-擷取連接配接對象的過程

incorporate

注冊資料源的過程相對簡單易懂,但有一個奇怪的地方在于,通常在注冊過程中,我們會期望有一種方法可以通過唯一辨別去查找資料源對象。然而,在C3P0中,即使我們知道了某個資料源的身份令牌(identity token),仍然無法直接擷取對應的資料源對象,因為C3P0Registry并沒有提供相關的方法。

後來我發現,我們既不能也不應該通過身份令牌來查找資料源,而是應該使用資料源的名稱(dataSourceName)進行查找。幸運的是,C3P0Registry提供了這樣的方法。是以,為了在程式的任何位置都能夠擷取到資料源對象,我們應該在建立資料源時就設定好它的資料源名稱。

通過使用資料源名稱作為唯一辨別,我們可以在需要的時候通過C3P0Registry來擷取資料源對象。這種方式更加直覺和友善,使得我們能夠輕松地管理和通路多個資料源。

C3P0資料連接配接池源碼-擷取連接配接對象的過程

pooledDataSourceByName

建立DriverManagerDataSource

C3P0資料連接配接池源碼-擷取連接配接對象的過程

UML

建立DriverManagerDataSource執行個體主要做了三件事:

1. 獲得this的identityToken,并注冊到C3P0Registry

2. 添加監聽配置參數改變的Listenner(當driverClass屬性更改時觸發事件)

3. 讀取配置檔案,初始化預設的user和password

C3P0資料連接配接池源碼-擷取連接配接對象的過程

建立DriverManagerDataSource

建立WrapperConnectionPoolDataSource

C3P0資料連接配接池源碼-擷取連接配接對象的過程

建立WrapperConnectionPoolDataSource,主要做了以下三件件事:

1. 獲得this的identityToken,并注冊到C3P0Registry

2. 添加監聽配置參數改變的Listenner(當connectionTesterClassName屬性更改時執行個體化ConnectionTester,當userOverridesAsString更改時重新解析字元串)

3. 解析userOverridesAsString

C3P0資料連接配接池源碼-擷取連接配接對象的過程

以上基本将ComboPooledDataSource的内容講完,下面介紹連接配接池的建立。

建立連接配接池對象

C3P0資料連接配接池源碼-擷取連接配接對象的過程

建立連接配接池的過程可以概括為四個步驟:

  1. 建立C3P0PooledConnectionPoolManager對象,開啟另一個線程來初始化timer、taskRunner、deferredStatementDestroyer、rpfact和authsToPools等屬性
  2. 建立預設賬号密碼對應的C3P0PooledConnectionPool對象,并建立PooledConnectionResourcePoolManager對象
  3. 建立BasicResourcePool對象,建立initialPoolSize對應的初始連接配接,開啟檢查連接配接是否過期、以及檢查空閑連接配接有效性的定時任務
C3P0資料連接配接池源碼-擷取連接配接對象的過程

當我們完成資料源的建立後,并沒有立即建立連接配接池。實際上,隻有在我們調用getConnection方法時,連接配接池才會被觸發建立。這是因為AbstractPoolBackedDataSource類實作了DataSource接口,其中包含了連接配接池的建立和管理邏輯。

AbstractPoolBackedDataSource作為連接配接池資料源的抽象實作,提供了一種統一的方式來管理連接配接池和資料源的關系。它将連接配接池的建立和初始化延遲到第一次請求連接配接時,這樣可以避免不必要的資源浪費。

在調用getConnection方法時,AbstractPoolBackedDataSource會檢查連接配接池是否已經建立,如果沒有,則會觸發連接配接池的建立過程。連接配接池的建立涉及到多個步驟,包括建立連接配接池對象、設定連接配接池的屬性和參數、初始化連接配接數等。這些步驟保證了連接配接池在被使用之前處于正确的狀态。

通過延遲建立連接配接池,我們能夠在真正需要連接配接時才進行資源的配置設定和管理,避免了不必要的資源消耗。這種設計思想可以提高系統的性能和效率,同時也使得連接配接池的使用更加靈活和可控。

C3P0資料連接配接池源碼-擷取連接配接對象的過程

下面介紹下這幾個類:

類名 描述
C3P0PooledConnectionPoolManager 連接配接池管理器。主要用于擷取/建立連接配接池,它持有DbAuth-C3P0PooledConnectionPool鍵值對的Map
C3P0PooledConnectionPool 連接配接池。主要用于檢入和檢出連接配接對象,實際調用的是其持有的BasicResourcePool對象
BasicResourcePool 資源池。主要用于檢入和檢出連接配接對象
PooledConnectionResourcePoolManager 資料總管。主要用于建立新的連接配接對象,以及檢入、檢出或空閑時進行連接配接測試

建立連接配接對象

我總結下擷取連接配接的過程,為以下幾步:

  1. 從BasicResourcePool的空閑連接配接中擷取,如果沒有,會嘗試去建立新的連接配接,當然,建立的過程也是異步的
  2. 開啟緩存語句支援
  3. 判斷連接配接是否正在被空閑資源檢測線程使用,如果是,重新擷取連接配接
  4. 校驗連接配接是否過期
  5. 檢出測試
  6. 判斷連接配接原來的Statement是不是已經清除完,如果沒有,重新擷取連接配接
  7. 設定監聽器後将連接配接傳回給用戶端

C3P0PooledConnectionPool.checkoutPooledConnection()

c3p0使用的是監聽器的方式,當用戶端調用close()方法時會觸發監聽器把連接配接checkin到連接配接池中。

C3P0資料連接配接池源碼-擷取連接配接對象的過程

C3P0PooledConnectionPool.checkoutAndMarkConnectionInUse()

用于從連接配接池中擷取連接配接,并标記該連接配接為正在使用狀态。

C3P0資料連接配接池源碼-擷取連接配接對象的過程

BasicResourcePool.checkoutResource(long)

在C3P0中負責從資源池中擷取資源的關鍵方法。這個方法在連接配接池中起着重要的作用,用于配置設定可用的資源給應用程式。

以下是大緻的checkoutResource(long)方法執行過程:

  1. 首先,方法會檢查資源池的狀态。如果資源池已關閉,則抛出異常,表示資源池不可用。
  2. 方法會嘗試從空閑資源清單中擷取一個可用的資源。空閑資源是指目前沒有被配置設定給應用程式的資源。如果有可用的空閑資源,則将其從空閑資源清單中移除,并傳回給應用程式。
  3. 如果沒有可用的空閑資源,方法會判斷目前已配置設定的資源數量是否達到資源池的最大容量限制。如果已達到最大容量限制,則進入等待狀态,直到有資源可用為止。
  4. 如果還有可用的資源(未達到最大容量限制),則根據資源池的配置和政策,建立一個新的資源,并将其配置設定給應用程式使用。
  5. 配置設定資源後,方法會更新資源的狀态,并進行必要的标記和記錄,以確定資源的正确配置設定和跟蹤。
C3P0資料連接配接池源碼-擷取連接配接對象的過程

BasicResourcePool.prelimCheckoutResource(long)

負責預檢查和準備資源配置設定的重要方法。在資源池中配置設定資源之前,該方法會執行一些預檢查和準備工作,以確定配置設定過程的正确性和可靠性。

以下是大緻的prelimCheckoutResource(long)方法執行過程:

  1. 首先,方法會檢查資源池的狀态。如果資源池已關閉,則抛出異常,表示資源池不可用。
  2. 方法會判斷是否需要在擷取資源之前執行驗證檢查。這可以通過配置來決定,例如testConnectionOnCheckout選項。如果需要執行驗證檢查,會調用BasicResourcePool.checkedOutConnectionTest()方法對資源進行驗證。
  3. 如果資源池中存在空閑資源,則直接傳回一個空閑資源。這樣可以避免建立新的資源對象,提高資源配置設定的效率。
  4. 如果沒有空閑資源可用,則繼續執行後續的資源配置設定邏輯。
  5. 方法會檢查目前已配置設定的資源數量是否達到資源池的最大容量限制。如果已達到最大容量限制,則進入等待狀态,直到有資源可用為止。
  6. 如果還有可用的資源(未達到最大容量限制),則根據資源池的配置和政策,建立一個新的資源,并将其标記為已配置設定狀态。
  7. 配置設定資源後,方法會進行必要的标記和記錄,以確定資源的正确配置設定和跟蹤。

BasicResourcePool._recheckResizePool()

重新檢查資源池的大小,并根據需要進行動态調整。

在C3P0中,資源池的大小可以根據配置進行自動調整,以适應應用程式的需求。這個自動調整的過程是通過監控資源池中的資源使用情況來實作的。當資源池的使用情況發生變化時,就會觸發大小調整機制。

C3P0資料連接配接池源碼-擷取連接配接對象的過程

BasicResourcePool.expandPool(int)

用于擴充資源池的大小。當資源池中的資源不足以滿足目前需求時,可以調用該方法來增加資源池的容量。方法的作用是向資源池中添加指定數量的新資源。它接受一個整數參數,表示要添加的資源數量。

C3P0資料連接配接池源碼-擷取連接配接對象的過程

它們的run方法中最終會去調用PooledConnectionResourcePoolManager的acquireResource方法。

PooledConnectionResourcePoolManager.acquireResource()

在該方法中,首先會擷取連接配接池管理器(ResourcePoolManager)的鎖,并檢查連接配接池管理器是否已關閉,如果已關閉則抛出異常。

接下來,通過調用ResourcePoolManager#checkoutResource(long)方法來擷取連接配接資源。這個方法是連接配接池管理器的核心方法,負責從連接配接池中擷取可用的資源。

在擷取連接配接資源之前,會檢查連接配接池管理器的狀态以及連接配接資源是否可用。如果連接配接資源不可用,會嘗試通過ResourcePoolManager#_recheckResizePool()方法來重新檢查并調整連接配接池的大小。

如果連接配接資源可用,就會标記該資源為已使用,并傳回給調用方。

最後,釋放連接配接池管理器的鎖,并傳回擷取到的連接配接資源。

C3P0資料連接配接池源碼-擷取連接配接對象的過程

WrapperConnectionPoolDataSource.getPooledConnection(String, String, ConnectionCustomizer, String)

用于擷取包裝後的連接配接池連接配接。

該方法接收四個參數:

  • user: 資料庫使用者名
  • password: 資料庫密碼
  • customizer: 連接配接定制器(可選)
  • dataSourceName: 資料源名稱(可選)

在方法内部,首先通過調用getPooledConnection(user, password)方法擷取到原始的連接配接池連接配接(PooledConnection)。

然後,通過調用wrapConnection方法将原始連接配接包裝為一個新的連接配接對象(WrapperConnection),并傳回給調用方。

包裝連接配接的過程可以應用一些定制邏輯,比如連接配接定制器可以在連接配接建立後對其進行自定義操作,例如設定連接配接屬性、執行初始化查詢等。最後,傳回包裝後的連接配接對象。

C3P0資料連接配接池源碼-擷取連接配接對象的過程

(此處已添加書籍卡片,請到今日頭條用戶端檢視)

繼續閱讀