Tomcat 的 JDBC 連接池

2022-03-03 14:08 更新

簡(jiǎn)介

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

使用方法

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

1. 附加功能

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

  • initSQL 當(dāng)連接創(chuàng)建后,能夠執(zhí)行一個(gè) SQL 語(yǔ)句(只執(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ì)涉及到同一屬性,也是用來(lái)將數(shù)據(jù)源配置成 bean 的。

4. JMX

連接池對(duì)象暴露了一個(gè)可以被注冊(cè)的 MBean。為了讓連接池對(duì)象創(chuàng)建 MBean,jmxEnabled 標(biāo)志必須設(shè)為 true。這并不是說(shuō)連接池會(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)用之前,一定要保證通過(guò)調(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 同樣的類加載器訪問(wèn)
    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í)間超過(guò) 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í))返回連接的最長(zhǎng)時(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 (布爾值)是否通過(guò)空閑對(duì)象清除者(如果存在的話)驗(yàn)證對(duì)象。如果對(duì)象驗(yàn)證失敗,則將其從池中清除。注意:為了讓 true 值生效,validationQuery 參數(shù)必須為非空字符串。該屬性默認(rèn)值為 false,為了運(yùn)行池的清除/測(cè)試線程,必須設(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ì))。通過(guò)在執(zhí)行 validationQuery 的語(yǔ)句上調(diào)用 java.sql.Statement.setQueryTimeout(seconds) 來(lái)實(shí)現(xiàn)。池本身并不會(huì)讓查詢超時(shí),完全是由 JDBC 來(lái)強(qiáng)制實(shí)現(xiàn)。若該值小于或等于 0,則禁用該功能。默認(rèn)為 -1。
    validatorClassName (字符串)實(shí)現(xiàn) org.apache.tomcat.jdbc.pool.Validator 接口并提供了一個(gè)無(wú)參(可能是隱式的)構(gòu)造函數(shù)的類名。如果指定該值,將通過(guò)該類來(lái)創(chuàng)建一個(gè) Validator 實(shí)例來(lái)驗(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 (布爾值)沒有用到的屬性??梢栽跉w入池內(nèi)的連接上調(diào)用 unwrap來(lái)訪問(wèn)。參閱 javax.sql.DataSource 接口的相關(guān)介紹,或者通過(guò)反射調(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ù)庫(kù)連接。另請(qǐng)參閱 logAbandoned。默認(rèn)值為 false。
    removeAbandonedTimeout (整型值)在廢棄連接(仍在使用)可以被清除之前的超時(shí)秒數(shù)。默認(rèn)為 60(60 秒)。應(yīng)把該值設(shè)定為應(yīng)用可能具有的運(yùn)行時(shí)間最長(zhǎng)的查詢。
    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——記錄打開的語(yǔ)句,并當(dāng)連接返回池后關(guān)閉它們。


  • 有關(guān)更多預(yù)定義攔截器的詳盡描述,可參閱JDBC 攔截器
    validationInterval 長(zhǎng)整型值。為避免過(guò)度驗(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á)順序來(lái)接收連接。
    在性能測(cè)試時(shí),鎖及鎖等待的實(shí)現(xiàn)方式有很大差異。當(dāng) fairQueue=true 時(shí),根據(jù)所運(yùn)行的操作系統(tǒng),存在一個(gè)決策過(guò)程。假如系統(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ù)目超過(guò) abandonWhenPercentageFull 中定義的百分比,否則不會(huì)關(guān)閉并報(bào)告已廢棄的連接(因?yàn)槌瑫r(shí))。取值范圍為 0-100。默認(rèn)值為 0,意味著只要達(dá)到 removeAbandonedTimeout,就應(yīng)關(guān)閉連接。
    maxAge 長(zhǎng)整型值。連接保持時(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)過(guò)配置,連接池還可以允許使用不同的憑證來(lái)請(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)求的憑證來(lái)開啟。按照這種方式,池的容量始終以全局級(jí)別管理,而不是限于模式(schema)級(jí)別。
    默認(rèn)值為 false。
    該屬性作為一個(gè)改進(jìn)方案,被添加到了 bug 50025 中。
    dataSource (javax.sql.DataSource)將數(shù)據(jù)源注入連接池,從而使池利用數(shù)據(jù)源來(lái)獲取連接,而不是利用 java.sql.Driver 接口來(lái)建立連接。它非常適于使用數(shù)據(jù)源(而非連接字符串)來(lái)池化 XA 連接或者已建立的連接時(shí)。默認(rèn)值為 null。
    dataSourceJNDI 字符串。在 JNDI 中查找的數(shù)據(jù)源的 JNDI 名稱,隨后將用于建立數(shù)據(jù)庫(kù)連接。參看 datasource 屬性的介紹。默認(rèn)值為 null。
    useDisposableConnectionFacade 布爾值。如果希望在連接上放上一個(gè)門面對(duì)象,從而使連接在關(guān)閉后無(wú)法重用,則要將值設(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è)簡(jiǎn)單的攔截器緩存了三個(gè)屬性:autoCommitreadOnly、transactionIsolation,為的是避免系統(tǒng)與數(shù)據(jù)庫(kù)之間無(wú)用的往返。

    當(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í)間超過(guò)預(yù)期時(shí)間時(shí)提供 JMX 通知。

    2. 配置 JDBC 攔截器

    JDBC 攔截器是通過(guò) jdbcInterceptor 屬性來(lái)配置的。該屬性值包含一列由分號(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

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

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

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

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

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

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

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

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

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

    它會(huì)針對(duì)每個(gè)連接對(duì)這些語(yǔ)句進(jìn)行緩存,然后計(jì)算池中所有連接的整體緩存數(shù),如果緩存數(shù)超過(guò)了限制 max,就不再對(duì)隨后的語(yǔ)句進(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 (以字符串形式表示的整型值)連接池中的緩存語(yǔ)句的數(shù)量限制。默認(rèn)為 50

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

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

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

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

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

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

    當(dāng)查詢超過(guò)失敗容差值時(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 容器外部是無(wú)效的。使用該類時(shí),默認(rèn)情況下,是通過(guò) ConnectionPool MBean 來(lái)發(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 來(lái)注冊(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 文檔

    簡(jiǎn)單的 Java

    下面這個(gè)簡(jiǎn)單的范例展示了如何創(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 連接池支持異步連接獲取,無(wú)需為池庫(kù)添加任何額外線程。這是通過(guò)在數(shù)據(jù)源上添加一個(gè)方法 Future<Connection> getConnectionAsync() 來(lái)實(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ì)于啟用、禁止或修改特定連接或其組件的功能而言,使用攔截器無(wú)疑是一種非常強(qiáng)大的方式。There are many different use cases for when interceptors are useful。默認(rèn)情況下,基于性能方面的考慮,連接池是無(wú)狀態(tài)的。連接池本身所插入的狀態(tài)是 defaultAutoCommitdefaultReadOnly、defaultTransactionIsolation,或 defaultCatalog(如果設(shè)置了這些狀態(tài))。這 4 個(gè)狀態(tài)只有在連接創(chuàng)建時(shí)才設(shè)置。無(wú)論這些屬性是否在連接使用期間被修改,池本身都不能重置它們。

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

      public JdbcInterceptor() {
      }  

    當(dāng)從連接池借出一個(gè)連接時(shí),攔截器能夠通過(guò)實(shí)現(xiàn)以下方法,初始化這一事件或以一些其他形式來(lái)響應(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ù)。通過(guò)觀察下面這個(gè)非常簡(jiǎn)單的例子,我們可以解釋如果當(dāng)連接已經(jīng)關(guān)閉時(shí),如何讓 java.sql.Connection.close() 的調(diào)用變得無(wú)用。

      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)前未連接到池中的攔截器來(lái)通知你。

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

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

    配置攔截器

    攔截器可以通過(guò) jdbcInterceptors 屬性或 setJdbcInterceptors 方法來(lái)配置。攔截器也可以有屬性,可以通過(guò)如下方式來(lái)配置:

      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 來(lái)構(gòu)建 JDBC 連接池代碼,但它也可以向后兼容到 1.5 運(yùn)行時(shí)環(huán)境。為了單元測(cè)試,使用 1.6 或更高版本。

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

    從源代碼構(gòu)建

    構(gòu)建非常簡(jiǎn)單。池依賴于 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 的源代碼倉(cāng)庫(kù)中。

    為了方便起見,在通過(guò)簡(jiǎn)單構(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ā)布組件,只有庫(kù)本身。

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

    掃描二維碼

    下載編程獅App

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

    編程獅公眾號(hào)