Tomcat 的 JDBC 連接池

2022-03-03 14:08 更新

簡介

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

那究竟為何需要一個(gè)新的連接池?

原因如下:

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

Tomcat JDBC 連接池還具有一些其他連接池實(shí)現(xiàn)所沒有的特點(diǎn):

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

使用方法

對(duì)于熟悉 Commons DBCP 的人來說,轉(zhuǎn)而使用 Tomcat 連接池是非常簡單的事。從其他連接池轉(zhuǎn)換過來也非常容易。

1. 附加功能

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

  • initSQL 當(dāng)連接創(chuàng)建后,能夠執(zhí)行一個(gè) SQL 語句(只執(zhí)行一次)。
  • validationInterval 恰當(dāng)?shù)卦谶B接上運(yùn)行驗(yàn)證,同時(shí)又能避免太多頻繁地執(zhí)行驗(yàn)證。
  • jdbcInterceptors 靈活并且可插拔的攔截器,能夠?qū)Τ剡M(jìn)行各種自定義,執(zhí)行各種查詢,處理結(jié)果集。下文將予以詳述。
  • fairQueue 將 fair 標(biāo)志設(shè)為 true,以達(dá)成線程公平性,或使用異步連接獲取。

2. Apache Tomcat 容器內(nèi)部

Tomcat JDBC 文檔中,Tomcat 連接池被配置為一個(gè)資源。唯一的區(qū)別在于,你必須指定 factory 屬性,并將其值設(shè)為 org.apache.tomcat.jdbc.pool.DataSourceFactory。

3. 獨(dú)立性

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

4. JMX

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

屬性

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

1. JNDI 工廠與類型

屬性 描述
factory 必需的屬性,其值應(yīng)為 org.apache.tomcat.jdbc.pool.DataSourceFactory
type 類型應(yīng)為 javax.sql.DataSourcejavax.sql.XADataSource。
根據(jù)類型,將創(chuàng)建org.apache.tomcat.jdbc.pool.DataSourceorg.apache.tomcat.jdbc.pool.XADataSource。

2. 系統(tǒng)屬性

系統(tǒng)屬性作用于 JVM 范圍,影響創(chuàng)建于 JVM 內(nèi)的所有池。

屬性 描述
org.apache.tomcat.jdbc.pool.onlyAttemptCurrentClassLoader 布爾值,默認(rèn)為 false。控制動(dòng)態(tài)類(如JDBC 驅(qū)動(dòng)、攔截器、驗(yàn)證器)的加載。如果采用默認(rèn)值,池會(huì)首先利用當(dāng)前類加載器(比如已經(jīng)加載池類的類加載器)加載類;如果類加載失敗,則嘗試?yán)镁€程上下文加載器加載。取值為 true 時(shí),會(huì)向后兼容 Apache Tomcat 8.0.8 及更早版本,只會(huì)采用當(dāng)前類加載器。如果未設(shè)置,則取默認(rèn)值。

3. 常用屬性

屬性 描述
defaultAutoCommit (布爾值)連接池所創(chuàng)建的連接默認(rèn)自動(dòng)提交狀態(tài)。如果未設(shè)置,則默認(rèn)采用 JDBC 驅(qū)動(dòng)的缺省值(如果未設(shè)置,則不會(huì)調(diào)用 setAutoCommit 方法)。
defaultReadOnly (布爾值)連接池所創(chuàng)建的連接默認(rèn)只讀狀態(tài)。如果未設(shè)置,將不會(huì)調(diào)用 setReadOnly 方法。(有些驅(qū)動(dòng)并不支持只讀模式,比如:informix)
defaultTransactionIsolation (字符串)連接池所創(chuàng)建的連接的默認(rèn)事務(wù)隔離狀態(tài)。取值范圍為:(參考 javadoc)
  • NONE
  • READ_COMMITTED
  • READ_UNCOMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

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

    4. Tomcat JDBC 增強(qiáng)屬性

    屬性 描述
    initSQL 字符串值。當(dāng)連接第一次創(chuàng)建時(shí),運(yùn)行的自定義查詢。默認(rèn)值為 null。
    jdbcInterceptors 字符串。繼承自類 org.apache.tomcat.jdbc.pool.JdbcInterceptor 的子類類名列表,由分號(hào)分隔。關(guān)于格式及范例,可參見下文的配置 JDBC 攔截器。

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

    預(yù)定義的攔截器有:
  • org.apache.tomcat.jdbc.pool.interceptor
  • ConnectionState——記錄自動(dòng)提交、只讀、catalog以及事務(wù)隔離級(jí)別等狀態(tài)。
  • org.apache.tomcat.jdbc.pool.interceptor
  • StatementFinalizer——記錄打開的語句,并當(dāng)連接返回池后關(guān)閉它們。


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

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

    高級(jí)用法

    1. JDBC 攔截器

    要想看看攔截器使用方法的具體范例,可以看看 org.apache.tomcat.jdbc.pool.interceptor.ConnectionState。這個(gè)簡單的攔截器緩存了三個(gè)屬性:autoCommit、readOnlytransactionIsolation,為的是避免系統(tǒng)與數(shù)據(jù)庫之間無用的往返。

    當(dāng)需求增加時(shí),姜維連接池核心增加更多的攔截器。歡迎貢獻(xiàn)你的才智!

    攔截器當(dāng)然并不局限于 java.sql.Connection,當(dāng)然也可以對(duì)方法調(diào)用的任何結(jié)果進(jìn)行包裝。你可以構(gòu)建查詢性能分析器,以便當(dāng)查詢運(yùn)行時(shí)間超過預(yù)期時(shí)間時(shí)提供 JMX 通知。

    2. 配置 JDBC 攔截器

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

    范例:
    jdbcInterceptors="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState; org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
    它實(shí)際上等同于:
    jdbcInterceptors="ConnectionState;StatementFinalizer"

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

    范例:

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

    系統(tǒng)會(huì)自動(dòng)忽略屬性名稱、屬性值以及類名前后多余的空格字符。

    org.apache.tomcat.jdbc.pool.JdbcInterceptor

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

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

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

    它能為下列屬性緩存連接:autoCommit、readOnly、transactionIsolationcatalog。這是一種性能增強(qiáng)功能,當(dāng)利用已設(shè)定的值來調(diào)用 getter 與 setter 時(shí),它能夠避免往返數(shù)據(jù)庫。

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

    跟蹤所有使用 createStatement、prepareStatementprepareCall 的語句,當(dāng)連接返回池后,關(guān)閉這些語句。

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

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

    緩存連接中的 PreparedStatementCallableStatement 實(shí)例。

    它會(huì)針對(duì)每個(gè)連接對(duì)這些語句進(jìn)行緩存,然后計(jì)算池中所有連接的整體緩存數(shù),如果緩存數(shù)超過了限制 max,就不再對(duì)隨后的語句進(jìn)行緩存,而是直接關(guān)閉它們。

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

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

    請(qǐng)參看 48392。攔截器會(huì)包裝語句和結(jié)果集,從而防止對(duì)使用了 ResultSet.getStatement().getConnection()Statement.getConnection() 方法的實(shí)際連接進(jìn)行訪問。

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

    當(dāng)新語句創(chuàng)建時(shí),自動(dòng)調(diào)用 java.sql.Statement.setQueryTimeout(seconds)。池本身并不會(huì)讓查詢超時(shí),完全是依靠 JDBC 驅(qū)動(dòng)來強(qiáng)制查詢超時(shí)。

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

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

    當(dāng)查詢超過失敗容差值時(shí),記錄查詢性能并發(fā)布日志項(xiàng)目。使用的日志級(jí)別為 WARN

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

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

    這是對(duì) SlowQueryReport 的擴(kuò)展,除了發(fā)布日志項(xiàng)目外,它還發(fā)布 JMX 通知,以便監(jiān)視工具作出相關(guān)反應(yīng)。該類從其父類繼承了所有屬性。它使用了 Tomcat 的 JMX 引擎,所以在 Tomcat 容器外部是無效的。使用該類時(shí),默認(rèn)情況下,是通過 ConnectionPool MBean 來發(fā)送 JMX 通知。如果 notifyPool=false,則 SlowQueryReportJmx 也可以注冊(cè)一個(gè) MBean。

    屬性 描述
    notifyPool (以字符串形式表示的布爾值)如果希望用 SlowQueryReportJmx MBean 發(fā)送 JMX 通知,則設(shè)為 false。默認(rèn)為 true
    objectName 字符串。定義一個(gè)有效的 javax.management.ObjectName 字符串,用于將這一對(duì)象注冊(cè)到平臺(tái)所用的 mbean 服務(wù)器上。默認(rèn)值為 null??梢允褂?tomcat.jdbc:type=org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx,name=the-name-of-the-pool 來注冊(cè)對(duì)象。

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

    當(dāng)連接簽出池中后,廢棄計(jì)時(shí)器即開始計(jì)時(shí)。這意味著如果超時(shí)為 30 秒,而你使用連接運(yùn)行了 10 個(gè) 10秒的查詢,那么它就會(huì)被標(biāo)為廢棄,并可能依靠 abandonWhenPercentageFull 屬性重新聲明。每次成功地在連接上執(zhí)行操作或執(zhí)行查詢時(shí),該攔截器就會(huì)重設(shè)簽出計(jì)時(shí)器。

    代碼范例

    其他 JDBC 用途的 Tomcat 配置范例可以參考 相關(guān)的 Tomcat 文檔。

    簡單的 Java

    下面這個(gè)簡單的范例展示了如何創(chuàng)建并使用數(shù)據(jù)源:

      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 連接池支持異步連接獲取,無需為池庫添加任何額外線程。這是通過在數(shù)據(jù)源上添加一個(gè)方法 Future<Connection> getConnectionAsync() 來實(shí)現(xiàn)的。為了使用異步獲取,必須滿足兩個(gè)條件:

    1. 必須把 failQueue 屬性設(shè)為 true。
    2. 必須把數(shù)據(jù)源轉(zhuǎn)換為 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");
    

    攔截器

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

    攔截器必須擴(kuò)展自 org.apache.tomcat.jdbc.pool.JdbcInterceptor 類。該類相當(dāng)簡單,你必須利用一個(gè)無參數(shù)構(gòu)造函數(shù)。

      public JdbcInterceptor() {
      }  

    當(dāng)從連接池借出一個(gè)連接時(shí),攔截器能夠通過實(shí)現(xiàn)以下方法,初始化這一事件或以一些其他形式來響應(yīng)該事件。

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

    上面這個(gè)方法有兩個(gè)參數(shù),一個(gè)是連接池本身的引用 ConnectionPool parent,一個(gè)是底層連接的引用 PooledConnection con。

    當(dāng)調(diào)用 java.sql.Connection 對(duì)象上的方法時(shí),會(huì)導(dǎo)致以下方法被調(diào)用:

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

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

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

    池啟動(dòng)與停止

    當(dāng)連接池開啟或關(guān)閉時(shí),你可以得到相關(guān)通知??赡苊總€(gè)攔截器類只通知一次,即使它是一個(gè)實(shí)例方法。也可能使用當(dāng)前未連接到池中的攔截器來通知你。

      public void poolStarted(ConnectionPool pool) {
      }
    
      public void poolClosed(ConnectionPool pool) {
      }
    

    當(dāng)重寫這些方法時(shí),如果你擴(kuò)展自 JdbcInterceptor 之外的類,不要忘記調(diào)用超類。

    配置攔截器

    攔截器可以通過 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()));
         }
      }
    

    獲取實(shí)際的 JDBC 連接

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

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

    構(gòu)建

    下面利用 1.6 來構(gòu)建 JDBC 連接池代碼,但它也可以向后兼容到 1.5 運(yùn)行時(shí)環(huán)境。為了單元測試,使用 1.6 或更高版本。

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

    從源代碼構(gòu)建

    構(gòu)建非常簡單。池依賴于 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
    

    構(gòu)建文件位于 Tomcat 的源代碼倉庫中。

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

      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)
    

    系統(tǒng)針對(duì) Maven 構(gòu)建進(jìn)行組織,但是沒有生成發(fā)布組件,只有庫本身。

    以上內(nèi)容是否對(duì)您有幫助:
    在線筆記
    App下載
    App下載

    掃描二維碼

    下載編程獅App

    公眾號(hào)
    微信公眾號(hào)

    編程獅公眾號(hào)