簡(jiǎn)單的MySQL連接池
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- />
當(dāng)tomcat讀到type="javax.sql.DataSource"屬性時(shí)會(huì)自動(dòng)重新安裝DBCP,除非你指定不同的factory。factory object 本身就是創(chuàng)建和配置連接池的。
在Apache Tomcat中有兩種方式配置 Resource elements
配置全局連接池
編輯conf/server.xml
- <GlobalNamingResources>
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- />
- </GlobalNamingResources>
然后你需要?jiǎng)?chuàng)建一個(gè)ResourceLink element使這個(gè)連接池對(duì)于web應(yīng)用是可用的。如果你想要用同一個(gè)名字讓連接池對(duì)于所有的應(yīng)用有效,最簡(jiǎn)單的方法就是編輯conf/context.xml文件
- <Context>
- <ResourceLink type="javax.sql.DataSource"
- name="jdbc/LocalTestDB"
- global="jdbc/TestDB"
- />
- <Context>
注意,如果你不想要全局的連接池,可以從server.xml移除Resource element到你的web應(yīng)用的context.xml 文件。
然后從剛配置好的連接池中獲得連接,簡(jiǎn)單java代碼:
- Context initContext = new InitialContext();
- Context envContext = (Context)initContext.lookup("java:/comp/env");
- DataSource datasource = (DataSource)envContext.lookup("jdbc/LocalTestDB");
- Connection con = datasource.getConnection();
使用java很簡(jiǎn)單
還可以使用Java syntax
- DataSource ds = new DataSource();
- ds.setDriverClassName("com.mysql.jdbc.Driver");
- ds.setUrl("jdbc:mysql://localhost:3306/mysql");
- ds.setUsername("root");
- ds.setPassword("password");
- PoolProperties pp = new PoolProperties();
- pp.setDriverClassName("com.mysql.jdbc.Driver");
- pp.setUrl("jdbc:mysql://localhost:3306/mysql");
- pp.setUsername("root");
- pp.setPassword("password");
- DataSource ds = new DataSource(pp);
設(shè)置連接池
我們將使用下面這些屬性設(shè)置連接池
- initialSize
- maxActive
- maxIdle
- minIdle
去了解這些屬性是很重要的,它們看起來(lái)很明顯但又有一些神秘
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- initialSize="10"
- maxActive="100"
- maxIdle="50"
- minIdle="10"
- />
initialSize=10 設(shè)置連接池建立時(shí)連接的數(shù)目
- 當(dāng)連接池定義在GlobalNamingResources中,連接池在Tomcat啟動(dòng)時(shí)創(chuàng)鍵
- 當(dāng)連接池定義在Context中,連接池在第一次查找JNDI時(shí)創(chuàng)建
maxActive=100 連接數(shù)據(jù)庫(kù)的最大連接數(shù)。這個(gè)屬性用來(lái)限制連接池中能夠打開(kāi)連接的數(shù)量,可以方便數(shù)據(jù)庫(kù)做連接容量規(guī)劃。
minIdle=10 連接池中存在的最小連接數(shù)目。連接池中連接數(shù)目可以變很少,如果使用了maxAge屬性,有些空閑的連接會(huì)被關(guān)閉因?yàn)殡x它最近一次連接的時(shí)間過(guò)去太久了。但是,我們看到的打開(kāi)的連接不會(huì)少于minIdle。
maxIdle屬性有一點(diǎn)麻煩。它的不同的行為取決于是否使用了pool sweeper。pool sweeper是一個(gè)可以在連接池正在使用的時(shí)候測(cè)試空閑連接和重置連接池大小的后臺(tái)線程。還負(fù)責(zé)檢測(cè)連接泄露。pool sweeper 通過(guò)如下方式定義的:
- public boolean isPoolSweeperEnabled() {
- boolean timer = getTimeBetweenEvictionRunsMillis()>0;
- boolean result = timer && (isRemoveAbandoned() && getRemoveAbandonedTimeout()>0);
- result = result || (timer && getSuspectTimeout()>0);
- result = result || (timer && isTestWhileIdle() && getValidationQuery()!=null);
- return result;
- }
sweeper每timeBetweenEvictionRunsMillis milliseconds運(yùn)行一次。
maxIdle定義如下
- Pool sweeper關(guān)閉,如果空閑連接池大于maxIdle,返回的連接將被關(guān)閉。
- Pool sweeper開(kāi)啟,空閑的連接數(shù)可以超過(guò)maxIdle,但如果連接空閑的時(shí)間已經(jīng)超過(guò)minEvictableIdleTimeMillis,能縮小到minIdle。聽(tīng)起來(lái)很奇怪連接池為什么不關(guān)閉連接當(dāng)空閑連接數(shù)量大于maxIdle。想想下面的情況:
- 100個(gè)線程處理100個(gè)并發(fā)請(qǐng)求
- 在一個(gè)請(qǐng)求中每個(gè)線程請(qǐng)求一個(gè)連接3次
在這種場(chǎng)景下,如果我們?cè)O(shè)置maxIdle=50,那么我們會(huì)關(guān)閉和打開(kāi)50*3的連接數(shù)。這樣增加了數(shù)據(jù)庫(kù)的負(fù)重并且減慢了應(yīng)用的速度。當(dāng)達(dá)到連接高峰時(shí),我們希望能夠充分利用連接池中的所有連接。因此,我們強(qiáng)烈希望打開(kāi)pool sweeper 。我們將在下一個(gè)部分探討具體的事項(xiàng)。我們?cè)谶@里額外說(shuō)明maxAge這個(gè)屬性。maxAge定義連接能夠打開(kāi)或者存在的時(shí)間,單位為毫秒。當(dāng)一個(gè)連接返回到了連接池,如果這個(gè)連接已經(jīng)使用過(guò),并且距離它第一次被使用的時(shí)間大于maxAge時(shí),這個(gè)連接會(huì)被關(guān)閉。
正如我們所看到的 isPoolSweeper算法實(shí)現(xiàn),sweeper 將會(huì)被打開(kāi),當(dāng)以下任一條件滿足時(shí)
- timeBetweenEvictionRunsMillis>0 AND removeAbandoned=true ANDremoveAbandonedTimeout>0
- timeBetweenEvictionRunsMillis>0 AND suspectTimeout>0
- timeBetweenEvictionRunsMillis>0 AND testWhileIdle=true AND validationQuery!=null
As of version 1.0.9 the following condition has been added
- timeBetweenEvictionRunsMillis>0 AND minEvictableIdleTimeMillis>0
(timer && getMinEvictableIdleTimeMillis()>0);
因此設(shè)置最理想的連接池,我們最好修改我們的配置滿足這些條件
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- initialSize="10"
- maxActive="100"
- maxIdle="50"
- minIdle="10"
- suspectTimeout="60"
- timeBetweenEvictionRunsMillis="30000"
- minEvictableIdleTimeMillis="60000"
- />
有效的連接
數(shù)據(jù)庫(kù)連接池提出了一個(gè)挑戰(zhàn),因?yàn)檫B接池中的連接會(huì)過(guò)時(shí)。這是常有的事,要么數(shù)據(jù)庫(kù),或者可能是連接池和數(shù)據(jù)庫(kù)中的一個(gè)設(shè)備,連接超時(shí)。唯一確定會(huì)話連接是活躍的真正辦法是使連接在服務(wù)器和數(shù)據(jù)庫(kù)做一個(gè)來(lái)回訪問(wèn)。在Java 6中,JDBC API處理驗(yàn)證連接是否是有效的方法是通過(guò)提供isValid變量來(lái)調(diào)用java.sql.Connection接口。在此之前,連接池不得不采用執(zhí)行一個(gè)查詢(xún)的方法,比如在MySQL上執(zhí)行SELECT 1.數(shù)據(jù)庫(kù)分析這句查詢(xún)很簡(jiǎn)單,不需要任何的磁盤(pán)訪問(wèn)。isValid被計(jì)劃實(shí)施,但 Apache Tomcat 6的連接池,也必須保存對(duì)Java 5的兼容性。
校驗(yàn)查詢(xún)
校驗(yàn)查詢(xún)會(huì)有一些挑戰(zhàn)
- 如果它們頻繁使用,會(huì)降低系統(tǒng)的性能
- 如果使用的間隔太久,會(huì)導(dǎo)致連接失效
- 如果應(yīng)用調(diào)用setTransactionIsolation并設(shè)置autoCommit=false,如果應(yīng)用再次調(diào)用setTransactionIsolation,會(huì)產(chǎn)生一個(gè)SQLException異常,因?yàn)樾r?yàn)查詢(xún)可能在數(shù)據(jù)庫(kù)中已經(jīng)產(chǎn)生了一個(gè)新的transaction。
讓我們看看最典型的配置:
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- testOnBorrow="true"
- validationQuery="SELECT 1"
- />
在這個(gè)配置中,java代碼每次調(diào)用 Connection con = dataSource.getConnection()時(shí)都會(huì)執(zhí)行一次SELECT 1查詢(xún)。
這樣保證了在連接提交給應(yīng)用之前都已經(jīng)測(cè)試過(guò)了。但是,對(duì)于在短時(shí)間內(nèi)頻繁使用連接的應(yīng)用,會(huì)對(duì)性能有嚴(yán)重的影響。這有兩個(gè)其他的配置選項(xiàng):
- testWhileIdle
- testOnReturn
當(dāng)在錯(cuò)誤的時(shí)間對(duì)連接做測(cè)試,它們也不是真正的很有幫助。
對(duì)于很多應(yīng)用來(lái)說(shuō),沒(méi)有校驗(yàn)不是一個(gè)真正的困難。一些應(yīng)用可以繞過(guò)校驗(yàn)通過(guò)設(shè)置minIdle=0和給minEvictableIdleTimeMillis一個(gè)很小的值,所以如果連接空閑了足夠長(zhǎng)的時(shí)間會(huì)讓數(shù)據(jù)庫(kù)會(huì)話超時(shí),在此之前連接池將會(huì)移除這些空閑太久的連接。
最好的解決辦法就是測(cè)試那些有一段時(shí)間沒(méi)被測(cè)試過(guò)的連接。
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- testOnBorrow="true"
- validationQuery="SELECT 1"
- validationInterval="30000"
- />
在這個(gè)配置中,連接校驗(yàn)的間隔不會(huì)超過(guò)30s。這是在性能和連接驗(yàn)證上的折中。正如前面提到的,如果我們想僥幸驗(yàn)證所有的連接,我們可以配置連接池中所有空閑連接超時(shí)。
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- timeBetweenEvictionRunsMillis="5000"
- minEvictableIdleTimeMillis="5000"
- minIdle="0"
- />
建立數(shù)據(jù)庫(kù)客戶會(huì)話
在一些案例中,當(dāng)初始化一個(gè)新的數(shù)據(jù)庫(kù)會(huì)話時(shí)需要執(zhí)行一些任務(wù)。可能包括執(zhí)行一個(gè)簡(jiǎn)單的SQL聲明或者執(zhí)行一個(gè)存儲(chǔ)過(guò)程。
當(dāng)你創(chuàng)建觸發(fā)器時(shí)候,這是在數(shù)據(jù)庫(kù)層面上的典型操作。
- create or replace trigger logon_alter_session after logon on database
- begin
- if sys_context('USERENV', 'SESSION_USER') = 'TEMP' then
- EXECUTE IMMEDIATE 'alter session ....';
- end if;
- end;
- /
這將影響所有的用戶,在后面這種情況下這是不夠的,當(dāng)創(chuàng)建一個(gè)新的會(huì)話的時(shí)候我們希望執(zhí)行一個(gè)自定義查詢(xún)。
- <Resource name="jdbc/TestDB" auth="Container"
- type="javax.sql.DataSource"
- description="Oracle Datasource"
- url="jdbc:oracle:thin:@//localhost:1521/orcl"
- driverClassName="oracle.jdbc.driver.OracleDriver"
- username="default_user"
- password="password"
- maxActive="100"
- validationQuery="select 1 from dual"
- validationInterval="30000"
- testOnBorrow="true"
- initSQL="ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY MM DD HH24:MI:SS'"/>
initSQL會(huì)被存在的每一條連接執(zhí)行。
連接池泄露和長(zhǎng)時(shí)間運(yùn)行的查詢(xún)
連接池包含一些診斷操作。jdbc-pool和Common DBCP都能夠檢測(cè)和減輕沒(méi)有返回連接池中的連接。這里演示是被稱(chēng)為拋出內(nèi)存泄露的連接。
- Connection con = dataSource.getConnection();
- Statement st = con.createStatement();
- st.executeUpdate("insert into id(value) values (1'); //SQLException here
- con.close();
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- maxActive="100"
- timeBetweenEvictionRunsMillis="30000"
- removeAbandoned="true"
- removeAbandonedTimeout="60"
- logAbandoned="true"
- />
- removeAbandoned-如果我們想檢測(cè)內(nèi)存泄露的連接,可以設(shè)置為true
- removeAbandonedTimeout-調(diào)用dataSource.getConnection開(kāi)始到丟棄檢測(cè)到泄露連接的時(shí)間(seconds)
- logAbandoned-如果想用log記錄丟棄的連接,可以設(shè)置為true。當(dāng)設(shè)置為true時(shí),調(diào)用dataSource.getConnection 時(shí)會(huì)記錄一個(gè)堆棧追蹤,并且被打印出來(lái)當(dāng)連接沒(méi)有返回的時(shí)候。
但我們想要這種類(lèi)型的診斷,當(dāng)然有可以使用的例子。也可以運(yùn)行批處理作業(yè)一次執(zhí)行一個(gè)連接幾分鐘。我們?cè)撊绾翁幚磉@些問(wèn)題?
兩個(gè)額外的選項(xiàng)已經(jīng)被加入來(lái)支持這些工作
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- maxActive="100"
- timeBetweenEvictionRunsMillis="30000"
- removeAbandoned="true"
- removeAbandonedTimeout="60"
- logAbandoned="true"
- abandonWhenPercentageFull="50"
- />
- abandonWhenPercentageFull-一條連接必須滿足臨界值 removeAbandonedTimeout和打開(kāi)連接的數(shù)量必須超過(guò)這個(gè)百分比。
使用這個(gè)屬性可能會(huì)在一次錯(cuò)誤判斷中產(chǎn)生在其他地方已經(jīng)被認(rèn)為丟棄的連接。設(shè)置這個(gè)值為100時(shí)意味著連接數(shù)除非到了maxActive限制時(shí),是不會(huì)被考慮丟棄的。這給連接池增加了一些靈活性,但是不會(huì)讓批處理作業(yè)使用單獨(dú)連接5分鐘。在這種情況,我們想確定當(dāng)我們檢測(cè)到連接仍然被使用時(shí),我們重置超時(shí)計(jì)時(shí)器,因此,連接不會(huì)被考慮丟棄。我們通過(guò)插入一個(gè)攔截器實(shí)現(xiàn)。
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- maxActive="100"
- timeBetweenEvictionRunsMillis="30000"
- removeAbandoned="true"
- removeAbandonedTimeout="60"
- logAbandoned="true"
- abandonWhenPercentageFull="50"
- jdbcInterceptors="ResetAbandonedTimer"
- />
攔截器在org.apache.tomcat.jdbc.pool.interceptor.ResetAbandonedTimer中被指定完全限定名稱(chēng),或者在org.apache.tomcat.jdbc.pool.interceptor包中使用短類(lèi)名
每次準(zhǔn)備語(yǔ)句或者執(zhí)行一次查詢(xún),連接池中的計(jì)時(shí)器會(huì)被重置放棄計(jì)時(shí)器。因?yàn)槿绱耍?分鐘的批處理作業(yè)中執(zhí)行多次查詢(xún)和更新,都不會(huì)超時(shí)。
這是你當(dāng)然想知道的情形,但你不會(huì)想去kill或者回收連接,因?yàn)槟悴粫?huì)知道會(huì)對(duì)你的系統(tǒng)產(chǎn)生什么影響。
- <Resource type="javax.sql.DataSource"
- name="jdbc/TestDB"
- factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/mysql"
- username="mysql_user"
- password="mypassword123"
- maxActive="100"
- timeBetweenEvictionRunsMillis="30000"
- logAbandoned="true"
- suspectTimeout="60"
- jdbcInterceptors="ResetAbandonedTimer"
- />
suspectTimeout屬性的工作方式與removeAbandonedTimeout 相似,除了不關(guān)閉連接,而只是簡(jiǎn)單的記錄警告和發(fā)布一個(gè)JMX通知信息。通過(guò)這種方式,你可以在不用改變你系統(tǒng)行為的情況下發(fā)現(xiàn)泄漏或者長(zhǎng)查詢(xún)。
從其它的數(shù)據(jù)源形成連接池
到目前為止我們處理連接池連接的獲得是通過(guò)java.sql.Driver接口。因此我們使用屬性
- driverClassName
- url
然而,一些連接配置是使用 javax.sql.DataSource 甚至是javax.sql.XADataSource接口,因此我們需要支持這些配置選項(xiàng)。
使用java相對(duì)是很容易的。
- PoolProperties pp = new PoolProperties();
- pp.setDataSource(myOtherDataSource);
- DataSource ds = new DataSource(pp);
- Connection con = ds.getConnection();
- DataSource ds = new DataSource();
- ds.setDataSource(myOtherDataSource);
- Connection con = ds.getConnection();
在我們處理XA連接時(shí)很方便。
在XML配置中,jdbc-pool會(huì)使用org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory類(lèi),一個(gè)能夠允許配置任何類(lèi)型的命名資源的簡(jiǎn)單類(lèi)。為了設(shè)置Apache Derby XADataSource 我們可以創(chuàng)建了下面的代碼
- <Resource factory="org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory"
- name="jdbc/DerbyXA1"
- type="org.apache.derby.jdbc.ClientXADataSource"
- databaseName="sample1"
- createDatabase="create"
- serverName="localhost"
- portNumber="1527"
- user="sample1"
- password="password"/>
這是一個(gè)簡(jiǎn)單的通過(guò)端口1527連接到網(wǎng)絡(luò)上的相鄰實(shí)例的XADataSource.
如果你想要從這個(gè)數(shù)據(jù)源形成XA連接池,我們可以在它后面建立這個(gè)連接池節(jié)點(diǎn)。
- <Resource factory="org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory"
- name="jdbc/DerbyXA1"
- type="org.apache.derby.jdbc.ClientXADataSource"
- databaseName="sample1"
- createDatabase="create"
- serverName="localhost"
- portNumber="1527"
- user="sample1"
- password="password"/>
- <Resource factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- dataSourceJNDI="DerbyXA1"<!--Links to the Derby XADataSource-->
- name="jdbc/TestDB1"
- auth="Container"
- type="javax.sql.XADataSource"
- testWhileIdle="true"
- testOnBorrow="true"
- testOnReturn="false"
- validationQuery="SELECT 1"
- validationInterval="30000"
- timeBetweenEvictionRunsMillis="5000"
- maxActive="100"
- minIdle="10"
- maxIdle="20"
- maxWait="10000"
- initialSize="10"
- removeAbandonedTimeout="60"
- removeAbandoned="true"
- logAbandoned="true"
- minEvictableIdleTimeMillis="30000"
- jmxEnabled="true"
- jdbcInterceptors="ConnectionState;StatementFinalizer;SlowQueryReportJmx(threshold=10000)"
- abandonWhenPercentageFull="75"/>
這里我們通過(guò)dataSourceJNDI=DerbyXA1屬性鏈接這兩個(gè)數(shù)據(jù)源。這兩個(gè)數(shù)據(jù)源都不得不存在同一個(gè)命名空間,在我們的例子中,是jdbc命名空間。
目前JNDI通過(guò)DataSource.setDataSourceJNDI(...)查找不被支持,只能通過(guò)factory對(duì)象。
如果你加入一個(gè)
- javax.sql.DataSource對(duì)象-連接池將會(huì)調(diào)用 javax.sql.DataSource.getConnection()方法
- javax.sql.DataSource 對(duì)象但是在連接池中指定了username/password-連接池將會(huì)調(diào)用javax.sql.DataSource.getConnection(String username, String password) 方法
- javax.sql.XADataSource對(duì)象-連接池將會(huì)調(diào)用 javax.sql.XADataSource.getXAConnection()方法
- javax.sql.XADataSource 對(duì)象但是在連接池中指定了 username/password-連接池將會(huì)調(diào)用 javax.sql.DataSource.getXAConnection(String username, String password)方法
這是一個(gè)有趣的現(xiàn)象當(dāng)你處理 XADataSources。你可以把返回的對(duì)象轉(zhuǎn)換為java.sql.Connection對(duì)象或者javax.sql.XAConnection對(duì)象,并且對(duì)同一個(gè)對(duì)象的兩個(gè)接口調(diào)用方法。
- DataSource ds = new DataSource();
- ds.setDataSource(myOtherDataSource);
- Connection con = ds.getConnection();
- if (con instanceof XAConnection) {
- XAConnection xacon = (XAConnection)con;
- transactionManager.enlistResource(xacon.getXAResource());
- }
- Statement st = con.createStatement();
- ResultSet rs = st.executeQuery(SELECT 1);
JDBC 攔截器
JDBC 攔截器創(chuàng)建是為了實(shí)現(xiàn)靈活性。javax.sql.PooledConnection 從底層驅(qū)動(dòng)封裝了java.sql.Connection/javax.sql.XAConnection或者數(shù)據(jù)源本身就是一個(gè)攔截器。攔截器以java.lang.reflect.InvocationHandler接口為基礎(chǔ)。攔截器是一個(gè)繼承自org.apache.tomcat.pool.jdbc.JdbcInterceptor的類(lèi)。
在本文中,我們將介紹如果配置攔截器。在我們下一篇文章,我們將介紹如果實(shí)現(xiàn)自定義攔截器和它們的生命周期。
- <Resource factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- ...
- jdbcInterceptors="ConnectionState;StatementFinalizer;SlowQueryReportJmx(threshold=10000)"
- />
與下面的相同
- <Resource factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
- ...
- jdbcInterceptors="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;
- org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer;
- org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx(threshold=10000)"
- />
攔截器可以使用一個(gè)短小的名稱(chēng),比如ConnectionState,如果這個(gè)攔截器定義在org.apache.tomcat.jdbc.pool.interceptor 包中。
否則,必須使用一個(gè)完全限定名稱(chēng)。
攔截器定義在以;分割的字符串中。攔截器可以在括號(hào)內(nèi)定義0個(gè)或多個(gè)參數(shù)。參數(shù)是以逗號(hào)分割的簡(jiǎn)單鍵值對(duì)。
連接狀態(tài)
java.sql.Connection接口有如下屬性
- autoCommit
- readOnly
- transactionIsolation
- catalog
這些屬性的默認(rèn)值可以使用如下的內(nèi)容為連接池配置
- defaultAutoCommit
- defaultReadOnly
- defaultTransactionIsolation
- defaultCatalog
如果設(shè)置了這些屬性,當(dāng)建立連接到數(shù)據(jù)庫(kù)時(shí)配置這個(gè)連接。如果沒(méi)有配置 ConnectionState攔截器,在建立連接時(shí)設(shè)置這些屬性會(huì)是一次性操作。如果配置了ConnectionState攔截器,每次從連接池取出的連接會(huì)將被重置為期望的狀態(tài)。
其中有些方法在執(zhí)行查詢(xún)時(shí)會(huì)導(dǎo)致往返數(shù)據(jù)庫(kù)。比如,調(diào)用 Connection.getTransactionIsolation()會(huì)導(dǎo)致驅(qū)動(dòng)查詢(xún)當(dāng)前會(huì)話的事務(wù)隔離級(jí)別。這種往返會(huì)導(dǎo)致嚴(yán)重的性能問(wèn)題并影響應(yīng)用在頻繁的使用連接執(zhí)行非常短和快的操作的時(shí)候。 ConnectionState 攔截器可以緩存這些操作的值并調(diào)用方法查詢(xún)它們從而避免往返數(shù)據(jù)庫(kù)。
Statement Finalizer
java代碼在使用java.sql對(duì)象后需要清除和釋放使用過(guò)的資源。
一個(gè)清理代碼示例
- Connection con = null;
- Statement st = null;
- ResultSet rs = null;
- try {
- con = ds.getConnection();
- ...
- } finally {
- if (rs!=null) try { rs.close(); } catch (Exception ignore){}
- if (st!=null) try { st.close(); } catch (Exception ignore){}
- if (con!=null) try { con.close();} catch (Exception ignore){}
- }
當(dāng)一個(gè)連接返回連接池的時(shí)候,StatementFinalizer攔截器確保 java.sql.Statement和它的子類(lèi)正確關(guān)閉。
獲得真正的JDBC連接
使用javax.sql.PooledConnection工具返回代理連接,因此取出連接十分直接,不需要轉(zhuǎn)換為特殊的類(lèi)。
同樣適用于你配置了處理javax.sql.XAConnection的連接池。
另一個(gè)有趣的取出底層連接的方法是
- Connection con = ds.getConnection();
- ction underlyingconnection = con.createStatement().getConnection();