Tomcat 的 JDBC 連接池


簡介

JDBC 連接池 org.apache.tomcat.jdbc.pool 是 Apache Commons DBCP 連接池的一種替換或備選方案。

那究竟為何需要一個新的連接池?

原因如下:

  1. Commons DBCP 1.x 是單線程。為了線程安全,在對象分配或對象返回的短期內,Commons 鎖定了全部池。但注意這並不適用於 Commons DBCP 2.x。
  2. Commons DBCP 1.x 可能會變得很慢。當邏輯 CPU 數目增長,或者試圖借出或歸還對象的並發線程增加時,性能就會受到影響。高並發系統受到的影響會更為顯著。注意這並不適用於 Commons DBCP 2.x。
  3. Commons DBCP 擁有 60 多個類。tomcat-jdbc-pool 核心只有 8 個類。因此為了未來需求變更着想,肯定需要更少的改動。我們真正需要的只是連接池本身,其余的只是附屬。
  4. Commons DBCP 使用靜態接口,因此對於指定版本的 JRE,只能采用正確版本的 DBCP,否則就會出現 NoSuchMethodException 異常。
  5. 當DBCP 可以用其他更簡便的實現來替代時,實在不值得重寫那 60 個類。
  6. Tomcat JDBC 連接池無需為庫本身添加額外線程,就能獲取異步獲取連接。
  7. Tomcat JDBC 連接池是 Tomcat 的一個模塊,依靠 Tomcat JULI 這個簡化了的日志架構。
  8. 使用 javax.sql.PooledConnection 接口獲取底層連接。
  9. 防止飢餓。如果池變空,線程將等待一個連接。當連接返回時,池就將喚醒正確的等待線程。大多數連接池只會一直維持飢餓狀態。

Tomcat JDBC 連接池還具有一些其他連接池實現所沒有的特點:

  1. 支持高並發環境與多核/CPU 系統。
  2. 接口的動態實現。支持 java.sql 與 java.sql 接口(只要 JDBC 驅動),甚至在利用低版本的 JDK 來編譯時。
  3. 驗證間隔時間。我們不必每次使用單個連接時都進行驗證,可以在借出或歸還連接時進行驗證,只要不低於我們所設定的間隔時間就行。
  4. 只執行一次查詢。當與數據庫建立起連接時,只執行一次的可配置查詢。這項功能對會話設置非常有用,因為你可能會想在連接建立的整個時段內都保持會話。
  5. 能夠配置自定義攔截器。通過自定義攔截器來增強功能。可以使用攔截器來采集查詢統計,緩存會話狀態,重新連接之前失敗的連接,重新查詢,緩存查詢結果,等等。由於可以使用大量的選項,所以這種自定義攔截器也是沒有限制的,跟 java.sql/javax.sql 接口的 JDK 版本沒有任何關系。
  6. 高性能。后文將舉例展示一些性能差異。
  7. 極其簡單。它的實現非常簡單,代碼行數與源文件都非常少,這都有賴於從一開始研發它時,就把簡潔當做重中之重。對比一下 c3p0 ,它的源文件超過了 200 個(最近一次統計),而 Tomcat JDBC 核心只有 8 個文件,連接池本身則大約只有這個數目的一半,所以能夠輕易地跟蹤和修改可能出現的 Bug。
  8. 異步連接獲取。可將連接請求隊列化,系統返回 Future<Connection>
  9. 更好地處理空閑連接。不再簡單粗暴地直接把空閑連接關閉,而是仍然把連接保留在池中,通過更為巧妙的算法控制空閑連接池的規模。
  10. 可以控制連接應被廢棄的時間:當池滿了即廢棄,或者指定一個池使用容差值,發生超時就進行廢棄處理。
  11. 通過查詢或語句來重置廢棄連接計時器。允許一個使用了很長時間的連接不因為超時而被廢棄。這一點是通過使用 ResetAbandonedTimer來實現的。
  12. 經過指定時間后,關閉連接。與返回池的時間相類似。
  13. 當連接要被釋放時,獲取 JMX 通知並記錄所有日志。它類似於 removeAbandonedTimeout,但卻不需要采取任何行為,只需要報告信息即可。通過 suspectTimeout 屬性來實現。
  14. 可以通過 java.sql.Driverjavax.sql.DataSource 或 javax.sql.XADataSource 獲取連接。通過 dataSource 與 dataSourceJNDI 屬性實現這一點。
  15. 支持 XA 連接。

使用方法

對於熟悉 Commons DBCP 的人來說,轉而使用 Tomcat 連接池是非常簡單的事。從其他連接池轉換過來也非常容易。

1. 附加功能

除了其他多數連接池能夠提供的功能外,Tomcat 連接池還提供了一些附加功能:

  • initSQL 當連接創建后,能夠執行一個 SQL 語句(只執行一次)。
  • validationInterval 恰當地在連接上運行驗證,同時又能避免太多頻繁地執行驗證。
  • jdbcInterceptors 靈活並且可插拔的攔截器,能夠對池進行各種自定義,執行各種查詢,處理結果集。下文將予以詳述。
  • fairQueue 將 fair 標志設為 true,以達成線程公平性,或使用異步連接獲取。

2. Apache Tomcat 容器內部

Tomcat JDBC 文檔中,Tomcat 連接池被配置為一個資源。唯一的區別在於,你必須指定 factory 屬性,並將其值設為 org.apache.tomcat.jdbc.pool.DataSourceFactory

3. 獨立性

連接池只有一個從屬文件,tomcat-juli.jar。要想在使用 bean 實例化的單一項目中使用池,實例化的 Bean 為org.apache.tomcat.jdbc.pool.DataSource。下文講到將連接池配置為 JNDI 資源時會涉及到同一屬性,也是用來將數據源配置成 bean 的。

4. JMX

連接池對象暴露了一個可以被注冊的 MBean。為了讓連接池對象創建 MBean,jmxEnabled 標志必須設為 true。這並不是說連接池會注冊到 MBean 服務器。在像 Tomcat 這樣的容器中,Tomcat 本身注冊就在 MBean 服務器上注冊了 DataSource。org.apache.tomcat.jdbc.pool.DataSource 對象會注冊實際的連接池 MBean。如果你在容器外運行,可以將 DataSource 注冊在任何你指定的對象名下,然后將這種注冊傳播到底層池。要想這樣做,你必須調用 mBeanServer.registerMBean(dataSource.getPool().getJmxPool(),objectname)。在調用之前,一定要保證通過調用 dataSource.createPool() 創建了池。

屬性

為了能夠順暢地在 Commons DBCP 與 Tomcat JDBC 連接池 之間轉換,大多數屬性名稱及其含義都是相同的。

1. JNDI 工廠與類型

屬性 描述
factory 必需的屬性,其值應為 org.apache.tomcat.jdbc.pool.DataSourceFactory
type 類型應為 javax.sql.DataSource 或 javax.sql.XADataSource
根據類型,將創建org.apache.tomcat.jdbc.pool.DataSource 或 org.apache.tomcat.jdbc.pool.XADataSource

2. 系統屬性

系統屬性作用於 JVM 范圍,影響創建於 JVM 內的所有池。

屬性 描述
org.apache.tomcat.jdbc.pool.onlyAttemptCurrentClassLoader 布爾值,默認為 false。控制動態類(如JDBC 驅動、攔截器、驗證器)的加載。如果采用默認值,池會首先利用當前類加載器(比如已經加載池類的類加載器)加載類;如果類加載失敗,則嘗試利用線程上下文加載器加載。取值為 true 時,會向后兼容 Apache Tomcat 8.0.8 及更早版本,只會采用當前類加載器。如果未設置,則取默認值。

3. 常用屬性

屬性 描述
defaultAutoCommit (布爾值)連接池所創建的連接默認自動提交狀態。如果未設置,則默認采用 JDBC 驅動的缺省值(如果未設置,則不會調用 setAutoCommit 方法)。
defaultReadOnly (布爾值)連接池所創建的連接默認只讀狀態。如果未設置,將不會調用 setReadOnly 方法。(有些驅動並不支持只讀模式,比如:informix)
defaultTransactionIsolation (字符串)連接池所創建的連接的默認事務隔離狀態。取值范圍為:(參考 javadoc)
  • NONE
  • READ_COMMITTED
  • READ_UNCOMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

如果未設置該值,則不會調用任何方法,默認為 JDBC 驅動。
defaultCatalog (字符串)連接池所創建的連接的默認catalog。
driverClassName (字符串)所要使用的 JDBC 驅動的完全限定的 Java 類名。該驅動必須能從與 tomcat-jdbc.jar 同樣的類加載器訪問
username (字符串)傳入 JDBC 驅動以便建立連接的連接用戶名。注意,DataSource.getConnection(username,password)方法默認不會使用傳入該方法內的憑證,但會使用這里的配置信息。可參看 alternateUsernameAllowed 了解更多詳情。
password (字符串)傳入 JDBC 驅動以便建立連接的連接密碼。注意,DataSource.getConnection(username,password)方法默認不會使用傳入該方法內的憑證,但會使用這里的配置信息。可參看 alternateUsernameAllowed 了解更多詳情。
maxActive (整形值)池同時能分配的活躍連接的最大數目。默認為 100
maxIdle (整型值)池始終都應保留的連接的最大數目。默認為 maxActive:100。會周期性檢查空閑連接(如果啟用該功能),留滯時間超過 minEvictableIdleTimeMillis 的空閑連接將會被釋放。(請參考 testWhileIdle
minIdle (整型值)池始終都應保留的連接的最小數目。如果驗證查詢失敗,則連接池會縮減該值。默認值取自 initialSize:10(請參考 testWhileIdle)。
initialSize (整型值)連接器啟動時創建的初始連接數。默認為 10
maxWait (整型值)在拋出異常之前,連接池等待(沒有可用連接時)返回連接的最長時間,以毫秒計。默認為 30000(30 秒)
testOnBorrow (布爾值)默認值為 false。從池中借出對象之前,是否對其進行驗證。如果對象驗證失敗,將其從池中清除,再接着去借下一個。注意:為了讓 true 值生效,validationQuery參數必須為非空字符串。為了實現更高效的驗證,可以采用 validationInterval
testOnReturn (布爾值)默認值為 false。將對象返回池之前,是否對齊進行驗證。注意:為了讓 true 值生效,validationQuery參數必須為非空字符串。
testWhileIdle (布爾值)是否通過空閑對象清除者(如果存在的話)驗證對象。如果對象驗證失敗,則將其從池中清除。注意:為了讓 true值生效,validationQuery 參數必須為非空字符串。該屬性默認值為 false,為了運行池的清除/測試線程,必須設置該值。(另請參閱 timeBetweenEvictionRunsMillis
validationQuery (字符串)在將池中連接返回給調用者之前,用於驗證這些連接的 SQL 查詢。如果指定該值,則該查詢不必返回任何數據,只是不拋出 SQLException 異常。默認為 null。實例值為:SELECT 1(MySQL) select 1 from dual(Oracle) SELECT 1(MySQL Server)。
validationQueryTimeout (整型值)連接驗證失敗前的超時時間(以秒計)。通過在執行 validationQuery 的語句上調用 java.sql.Statement.setQueryTimeout(seconds) 來實現。池本身並不會讓查詢超時,完全是由 JDBC 來強制實現。若該值小於或等於 0,則禁用該功能。默認為 -1
validatorClassName (字符串)實現 org.apache.tomcat.jdbc.pool.Validator接口並提供了一個無參(可能是隱式的)構造函數的類名。如果指定該值,將通過該類來創建一個 Validator 實例來驗證連接,代替任何驗證查詢。默認為 null,范例值為:com.mycompany.project.SimpleValidator
timeBetweenEvictionRunsMillis (整型值)空閑連接驗證/清除線程運行之間的休眠時間(以毫秒計)。不能低於 1 秒。該值決定了我們檢查空閑連接、廢棄連接的頻率,以及驗證空閑連接的頻率。默認為 5000(5 秒)
numTestsPerEvictionRun (整型值)Tomcat JDBC 連接池沒有用到這個屬性。
minEvictableIdleTimeMillis (整型值)在被確定應被清除之前,對象在池中保持空閑狀態的最短時間(以毫秒計)。默認為 60000(60 秒)
accessToUnderlyingConnectionAllowed (布爾值)沒有用到的屬性。可以在歸入池內的連接上調用 unwrap來訪問。參閱 javax.sql.DataSource 接口的相關介紹,或者通過反射調用 getConnection,或者將對象映射為 javax.sql.PooledConnection
removeAbandoned (布爾值)該值為標志(Flag)值,表示如果連接時間超出了 removeAbandonedTimeout,則將清除廢棄連接。如果該值被設置為 true,則如果連接時間大於 removeAbandonedTimeout,該連接會被認為是廢棄連接,應予以清除。若應用關閉連接失敗時,將該值設為 true 能夠恢復該應用的數據庫連接。另請參閱 logAbandoned。默認值為 false
removeAbandonedTimeout (整型值)在廢棄連接(仍在使用)可以被清除之前的超時秒數。默認為 60(60 秒)。應把該值設定為應用可能具有的運行時間最長的查詢。
logAbandoned (布爾值)標志能夠針對丟棄連接的應用代碼,進行堆棧跟蹤記錄。由於生成堆棧跟蹤,對廢棄連接的日志記錄會增加每一個借取連接的開銷。默認為 false
connectionProperties (字符串)在建立新連接時,發送給 JDBC 驅動的連接屬性。字符串格式必須為:[propertyName=property;]*。注意:user 與 password 屬性會顯式傳入,因此這里並不需要包括它們。默認為 null。
poolPreparedStatements (布爾值)未使用的屬性
maxOpenPreparedStatements (整型值)未使用的屬性

4. Tomcat JDBC 增強屬性

屬性 描述
initSQL 字符串值。當連接第一次創建時,運行的自定義查詢。默認值為 null
jdbcInterceptors 字符串。繼承自類 org.apache.tomcat.jdbc.pool.JdbcInterceptor的子類類名列表,由分號分隔。關於格式及范例,可參見下文的配置 JDBC 攔截器。

這些攔截器將會插入到 java.sql.Connection 對象的操作隊列中。 

預定義的攔截器有:
  • org.apache.tomcat.jdbc.pool.interceptor
  • ConnectionState——記錄自動提交、只讀、catalog以及事務隔離級別等狀態。
  • org.apache.tomcat.jdbc.pool.interceptor
  • StatementFinalizer——記錄打開的語句,並當連接返回池后關閉它們。


有關更多預定義攔截器的詳盡描述,可參閱JDBC 攔截器
validationInterval 長整型值。為避免過度驗證而設定的頻率時間值(以秒計)。最多以這種頻率運行驗證。如果連接應該進行驗證,但卻沒能在此間隔時間內得到驗證,則會重新對其進行驗證。默認為 30000(30 秒)。
jmxEnabled 布爾值。是否利用 JMX 注冊連接池。默認為 true
fairQueue 布爾值。假如想用真正的 FIFO 方式公平對待 getConnection 調用,則取值為 true。對空閑連接列表將采用 org.apache.tomcat.jdbc.pool.FairBlockingQueue 實現。默認值為 true。如果想使用異步連接獲取功能,則必須使用該標志。
設置該標志可保證線程能夠按照連接抵達順序來接收連接。
在性能測試時,鎖及鎖等待的實現方式有很大差異。當 fairQueue=true 時,根據所運行的操作系統,存在一個決策過程。假如系統運行在 Linux 操作系統(屬性 os.name = linux)上,為了禁止這個 Linux 專有行為,但仍想使用公平隊列,那么只需在連接池類加載之前,將 org.apache.tomcat.jdbc.pool.FairBlockingQueue.ignoreOS=true添加到系統屬性上。
abandonWhenPercentageFull 整型值。除非使用中連接的數目超過 abandonWhenPercentageFull中定義的百分比,否則不會關閉並報告已廢棄的連接(因為超時)。取值范圍為 0-100。默認值為 0,意味着只要達到 removeAbandonedTimeout,就應關閉連接。
maxAge 長整型值。連接保持時間(以毫秒計)。當連接要返回池中時,連接池會檢查是否達到 now - time-when-connected > maxAge 的條件,如果條件達成,則關閉該連接,不再將其返回池中。默認值為 0,意味着連接將保持開放狀態,在將連接返回池中時,不會執行任何年齡檢查。
useEquals 布爾值。如果想讓 ProxyConnection 類使用 String.equals,則將該值設為 true;若想在對比方法名稱時使用 ==,則應將其設為 false。該屬性不能用於任何已添加的攔截器中,因為那些攔截器都是分別配置的。默認值為 true
suspectTimeout 整型值。超時時間(以秒計)。默認值為 0
類似於 removeAbandonedTimeout,但不會把連接當做廢棄連接從而有可能關閉連接。如果 logAbandoned 設為 true,它只會記錄下警告。如果該值小於或等於 0,則不會執行任何懷疑式檢查。如果超時值大於 0,而連接還沒有被廢棄,或者廢棄檢查被禁用時,才會執行懷疑式檢查。如果某個連接被懷疑到,則記錄下 WARN 信息並發送一個 JMX 通知。
rollbackOnReturn 布爾值。如果 autoCommit==false,那么當連接返回池中時,池會在連接上調用回滾方法,從而終止事務。默認值為 false
commitOnReturn 布爾值。如果 autoCommit==false,那么當連接返回池中時,池會在連接上調用提交方法,從而完成事務;如果 rollbackOnReturn==true,則忽略該屬性。默認值為 false
alternateUsernameAllowed 布爾值。出於性能考慮,JDBC 連接池默認會忽略 DataSource.getConnection(username,password)調用,只返回之前池化的具有全局配置屬性 username 和 password的連接。

但經過配置,連接池還可以允許使用不同的憑證來請求每一個連接。為了啟用這項在DataSource.getConnection(username,password)調用中描述的功能,只需將 alternateUsernameAllowed 設為 true
如果你請求一個連接,憑證為 user 1/password 1,而連接之前使用的是 user 2/password 2 憑證,那么連接將被關閉,重新利用請求的憑證來開啟。按照這種方式,池的容量始終以全局級別管理,而不是限於模式(schema)級別。
默認值為 false
該屬性作為一個改進方案,被添加到了 bug 50025 中。
dataSource (javax.sql.DataSource)將數據源注入連接池,從而使池利用數據源來獲取連接,而不是利用 java.sql.Driver接口來建立連接。它非常適於使用數據源(而非連接字符串)來池化 XA 連接或者已建立的連接時。默認值為 null
dataSourceJNDI 字符串。在 JNDI 中查找的數據源的 JNDI 名稱,隨后將用於建立數據庫連接。參看 datasource 屬性的介紹。默認值為 null
useDisposableConnectionFacade 布爾值。如果希望在連接上放上一個門面對象,從而使連接在關閉后無法重用,則要將值設為 true。這能防止線程繼續引用一個已被關閉的連接,並繼續在連接上查詢。默認值為 true
logValidationErrors 布爾值。設為 true 時,能將驗證階段的錯誤記錄到日志文件中,錯誤會被記錄為 SEVERE。考慮到了向后兼容性,默認值為 false
propagateInterruptState 布爾值。傳播已中斷的線程(還沒有清除中斷狀態)的中斷狀態。考慮到了向后兼容性,默認值為 false
ignoreExceptionOnPreLoad 布爾值。在初始化池時,是否忽略連接創建錯誤。取值為 true時表示忽略;設為 false 時,拋出異常,從而宣告池初始化失敗。默認值為 false

高級用法

1. JDBC 攔截器

要想看看攔截器使用方法的具體范例,可以看看 org.apache.tomcat.jdbc.pool.interceptor.ConnectionState。這個簡單的攔截器緩存了三個屬性:autoCommitreadOnlytransactionIsolation,為的是避免系統與數據庫之間無用的往返。

當需求增加時,姜維連接池核心增加更多的攔截器。歡迎貢獻你的才智!

攔截器當然並不局限於 java.sql.Connection,當然也可以對方法調用的任何結果進行包裝。你可以構建查詢性能分析器,以便當查詢運行時間超過預期時間時提供 JMX 通知。

2. 配置 JDBC 攔截器

JDBC 攔截器是通過 jdbcInterceptor 屬性來配置的。該屬性值包含一列由分號分隔的類名。如果這些類名非完全限定,就會在它們的前面加上 org.apache.tomcat.jdbc.pool.interceptor. 前綴。

范例:
jdbcInterceptors="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState; org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
它實際上等同於:
jdbcInterceptors="ConnectionState;StatementFinalizer"

攔截器也同樣有屬性。攔截器的屬性指定在類名后的括號里,如果設置多個屬性,則用逗號分隔開。

范例:

jdbcInterceptors="ConnectionState;StatementFinalizer(useEquals=true)"

系統會自動忽略屬性名稱、屬性值以及類名前后多余的空格字符。

org.apache.tomcat.jdbc.pool.JdbcInterceptor

所有攔截器的抽象基類,無法實例化。

屬性 描述
useEquals (布爾值)如果希望 ProxyConnection 類使用 String.equals,則設為 true;當希望在對比方法名時使用 ==,則設為 false。默認為 true

org.apache.tomcat.jdbc.pool.interceptor.ConnectionState

它能為下列屬性緩存連接:autoCommitreadOnlytransactionIsolation 及 catalog。這是一種性能增強功能,當利用已設定的值來調用 getter 與 setter 時,它能夠避免往返數據庫。

org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer

跟蹤所有使用 createStatementprepareStatement 或 prepareCall 的語句,當連接返回池后,關閉這些語句。

屬性 描述
trace (以字符串形式表示的布爾值)對未關閉語句進行跟蹤。當啟用跟蹤且連接被關閉時,如果相關語句沒有關閉,則攔截器會記錄所有的堆棧跟蹤。默認值為 false

org.apache.tomcat.jdbc.pool.interceptor.StatementCache

緩存連接中的 PreparedStatement 或 CallableStatement 實例。

它會針對每個連接對這些語句進行緩存,然后計算池中所有連接的整體緩存數,如果緩存數超過了限制 max,就不再對隨后的語句進行緩存,而是直接關閉它們。

屬性 描述
prepared (以字符串形式表示的布爾值)對使用 prepareStatement 調用創建的 PreparedStatement實例進行緩存。默認為 true
callable (以字符串形式表示的布爾值)對使用 prepareCall 調用創建的 CallableStatement實例進行緩存。默認為 false
max (以字符串形式表示的整型值)連接池中的緩存語句的數量限制。默認為 50

org.apache.tomcat.jdbc.pool.interceptor.StatementDecoratorInterceptor

請參看 48392。攔截器會包裝語句和結果集,從而防止對使用了 ResultSet.getStatement().getConnection() 和 Statement.getConnection() 方法的實際連接進行訪問。

org.apache.tomcat.jdbc.pool.interceptor.QueryTimeoutInterceptor

當新語句創建時,自動調用 java.sql.Statement.setQueryTimeout(seconds)。池本身並不會讓查詢超時,完全是依靠 JDBC 驅動來強制查詢超時。

屬性 描述
queryTimeout (以字符串形式表示的整型值)查詢超時的毫秒數。默認為 1000 毫秒。

org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport

當查詢超過失敗容差值時,記錄查詢性能並發布日志項目。使用的日志級別為 WARN

屬性 描述
threshold (以字符串形式表示的整型值)查詢應超時多少毫秒才發布日志警告。默認為 1000 毫秒
maxQueries (以字符串形式表示的整型值)為保留內存空間,所能記錄的最大查詢數量。默認為 1000
logSlow (以字符串形式表示的布爾值)如果想記錄較慢的查詢,設為 true。默認為 true
logFailed (以字符串形式表示的布爾值)如果想記錄失敗查詢,設為 true。默認為 true

org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx

這是對 SlowQueryReport 的擴展,除了發布日志項目外,它還發布 JMX 通知,以便監視工具作出相關反應。該類從其父類繼承了所有屬性。它使用了 Tomcat 的 JMX 引擎,所以在 Tomcat 容器外部是無效的。使用該類時,默認情況下,是通過 ConnectionPool MBean 來發送 JMX 通知。如果 notifyPool=false,則 SlowQueryReportJmx 也可以注冊一個 MBean。

屬性 描述
notifyPool (以字符串形式表示的布爾值)如果希望用 SlowQueryReportJmx MBean 發送 JMX 通知,則設為 false。默認為 true
objectName 字符串。定義一個有效的 javax.management.ObjectName 字符串,用於將這一對象注冊到平台所用的 mbean 服務器上。默認值為 null。可以使用 tomcat.jdbc:type=org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx,name=the-name-of-the-pool 來注冊對象。

org.apache.tomcat.jdbc.pool.interceptor.ResetAbandonedTimer

當連接簽出池中后,廢棄計時器即開始計時。這意味着如果超時為 30 秒,而你使用連接運行了 10 個 10秒的查詢,那么它就會被標為廢棄,並可能依靠 abandonWhenPercentageFull 屬性重新聲明。每次成功地在連接上執行操作或執行查詢時,該攔截器就會重設簽出計時器。

代碼范例

其他 JDBC 用途的 Tomcat 配置范例可以參考 相關的 Tomcat 文檔

簡單的 Java

下面這個簡單的范例展示了如何創建並使用數據源:

  import java.sql.Connection;
  import java.sql.ResultSet;
  import java.sql.Statement;

  import org.apache.tomcat.jdbc.pool.DataSource;
  import org.apache.tomcat.jdbc.pool.PoolProperties;

  public class SimplePOJOExample {

      public static void main(String[] args) throws Exception {
          PoolProperties p = new PoolProperties();
          p.setUrl("jdbc:mysql://localhost:3306/mysql");
          p.setDriverClassName("com.mysql.jdbc.Driver");
          p.setUsername("root");
          p.setPassword("password");
          p.setJmxEnabled(true);
          p.setTestWhileIdle(false);
          p.setTestOnBorrow(true);
          p.setValidationQuery("SELECT 1");
          p.setTestOnReturn(false);
          p.setValidationInterval(30000);
          p.setTimeBetweenEvictionRunsMillis(30000);
          p.setMaxActive(100);
          p.setInitialSize(10);
          p.setMaxWait(10000);
          p.setRemoveAbandonedTimeout(60);
          p.setMinEvictableIdleTimeMillis(30000);
          p.setMinIdle(10);
          p.setLogAbandoned(true);
          p.setRemoveAbandoned(true);
          p.setJdbcInterceptors(
            "org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"+
            "org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer");
          DataSource datasource = new DataSource();
          datasource.setPoolProperties(p);

          Connection con = null;
          try {
            con = datasource.getConnection();
            Statement st = con.createStatement();
            ResultSet rs = st.executeQuery("select * from user");
            int cnt = 1;
            while (rs.next()) {
                System.out.println((cnt++)+". Host:" +rs.getString("Host")+
                  " User:"+rs.getString("User")+" Password:"+rs.getString("Password"));
            }
            rs.close();
            st.close();
          } finally {
            if (con!=null) try {con.close();}catch (Exception ignore) {}
          }
      }

  }

作為資源使用

下例展示了如何為 JNDI 查找配置資源。

<Resource name="jdbc/TestDB"
          auth="Container"
          type="javax.sql.DataSource"
          factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
          testWhileIdle="true"
          testOnBorrow="true"
          testOnReturn="false"
          validationQuery="SELECT 1"
          validationInterval="30000"
          timeBetweenEvictionRunsMillis="30000"
          maxActive="100"
          minIdle="10"
          maxWait="10000"
          initialSize="10"
          removeAbandonedTimeout="60"
          removeAbandoned="true"
          logAbandoned="true"
          minEvictableIdleTimeMillis="30000"
          jmxEnabled="true"
          jdbcInterceptors="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;
            org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
          username="root"
          password="password"
          driverClassName="com.mysql.jdbc.Driver"
          url="jdbc:mysql://localhost:3306/mysql"/>

異步連接獲取

Tomcat JDBC 連接池支持異步連接獲取,無需為池庫添加任何額外線程。這是通過在數據源上添加一個方法 Future<Connection> getConnectionAsync() 來實現的。為了使用異步獲取,必須滿足兩個條件:

  1. 必須把 failQueue 屬性設為 true
  2. 必須把數據源轉換為 org.apache.tomcat.jdbc.pool.DataSource

下例就使用了異步獲取功能:

  Connection con = null;
  try {
    Future<Connection> future = datasource.getConnectionAsync();
    while (!future.isDone()) {
      System.out.println("Connection is not yet available. Do some background work");
      try {
        Thread.sleep(100); //simulate work
      }catch (InterruptedException x) {
        Thread.currentThread().interrupt();
      }
    }
    con = future.get(); //should return instantly
    Statement st = con.createStatement();
    ResultSet rs = st.executeQuery("select * from user");

攔截器

對於啟用、禁止或修改特定連接或其組件的功能而言,使用攔截器無疑是一種非常強大的方式。There are many different use cases for when interceptors are useful。默認情況下,基於性能方面的考慮,連接池是無狀態的。連接池本身所插入的狀態是 defaultAutoCommitdefaultReadOnlydefaultTransactionIsolation,或 defaultCatalog(如果設置了這些狀態)。這 4 個狀態只有在連接創建時才設置。無論這些屬性是否在連接使用期間被修改,池本身都不能重置它們。

攔截器必須擴展自 org.apache.tomcat.jdbc.pool.JdbcInterceptor 類。該類相當簡單,你必須利用一個無參數構造函數。

  public JdbcInterceptor() {
  }  

當從連接池借出一個連接時,攔截器能夠通過實現以下方法,初始化這一事件或以一些其他形式來響應該事件。

public abstract void reset(ConnectionPool parent, PooledConnection con);

上面這個方法有兩個參數,一個是連接池本身的引用 ConnectionPool parent,一個是底層連接的引用 PooledConnection con

當調用 java.sql.Connection 對象上的方法時,會導致以下方法被調用:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

Method method 是被調用的實際方法,Object[] args 是參數。通過觀察下面這個非常簡單的例子,我們可以解釋如果當連接已經關閉時,如何讓 java.sql.Connection.close() 的調用變得無用。

  if (CLOSE_VAL==method.getName()) {
      if (isClosed()) return null; //noop for already closed.
  }
  return super.invoke(proxy,method,args);  

池啟動與停止

當連接池開啟或關閉時,你可以得到相關通知。可能每個攔截器類只通知一次,即使它是一個實例方法。也可能使用當前未連接到池中的攔截器來通知你。

  public void poolStarted(ConnectionPool pool) {
  }

  public void poolClosed(ConnectionPool pool) {
  }

當重寫這些方法時,如果你擴展自 JdbcInterceptor 之外的類,不要忘記調用超類。

配置攔截器

攔截器可以通過 jdbcInterceptors 屬性或 setJdbcInterceptors 方法來配置。攔截器也可以有屬性,可以通過如下方式來配置:

  String jdbcInterceptors=
    "org.apache.tomcat.jdbc.pool.interceptor.ConnectionState(useEquals=true,fast=yes)"

攔截器屬性

既然攔截器也有屬性,那么你也可以讀取其中的屬性值。你可以重寫 setProperties 方法。

  public void setProperties(Map<String, InterceptorProperty> properties) {
     super.setProperties(properties);
     final String myprop = "myprop";
     InterceptorProperty p1 = properties.get(myprop);
     if (p1!=null) {
         setMyprop(Long.parseLong(p1.getValue()));
     }
  }

獲取實際的 JDBC 連接

連接池圍繞實際的連接創建包裝器,為的是能夠正確地池化。同樣,為了執行特定的功能,我們也可以在這些包裝器中創建攔截器。如果不需要獲取實際的連接,可以使用 javax.sql.PooledConnection 接口。

  Connection con = datasource.getConnection();
  Connection actual = ((javax.sql.PooledConnection)con).getConnection();

構建

下面利用 1.6 來構建 JDBC 連接池代碼,但它也可以向后兼容到 1.5 運行時環境。為了單元測試,使用 1.6 或更高版本。

更多的關於 JDBC 用途的 Tomcat 配置范例可參看 [Tomcat 文檔]()。

從源代碼構建

構建非常簡單。池依賴於 tomcat-juli.jar,在這種情況下,需要 SlowQueryReportJmx

  javac -classpath tomcat-juli.jar \
        -d . \
        org/apache/tomcat/jdbc/pool/*.java \
        org/apache/tomcat/jdbc/pool/interceptor/*.java \
        org/apache/tomcat/jdbc/pool/jmx/*.java

構建文件位於 Tomcat 的源代碼倉庫中。

為了方便起見,在通過簡單構建命令生成所需文件的地方也包含了一個構建文件。

  ant download  (downloads dependencies)
  ant build     (compiles and generates .jar files)
  ant dist      (creates a release package)
  ant test      (runs tests, expects a test database to be setup)

系統針對 Maven 構建進行組織,但是沒有生成發布組件,只有庫本身。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM