Tomcat 連接池詳解


(轉)

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)調用中描述的功能,只需將 alternateUsernameAllowed 設為 true
如果你請求一個連接,憑證為 user 1/password 1,而連接之前使用的是 user 2/password 2 憑證,那么連接將被關閉,重新利用請求的憑證來開啟。按照這種方式,池的容量始終以全局級別管理,而不是限於模式(schema)級別。
默認值為 false
該屬性作為一個改進方案,被添加到了 <a rel="nofollow" href="https://bz.apache.org/bugzilla/show_bug.cgi?id=50025" "="" style="box-sizing: border-box; color: rgb(45, 133, 202); text-decoration: none; ">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

請參看 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 的<a rel="nofollow" href="http://svn.apache.org/viewvc/tomcat/trunk/modules/jdbc-pool/" "="" style="box-sizing: border-box; color: rgb(45, 133, 202); text-decoration: none; ">源代碼倉庫中。

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

  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