吾非文人,乃市井一俗人也,讀百卷書,跨江河千里,故申城一游; 一兩滴辛酸,三四年學(xué)業(yè),五六點(diǎ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" />
當(dāng)tomcat讀到type="javax.sql.DataSource"屬性時(shí)會自動重新安裝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>
<Context> <ResourceLink type="javax.sql.DataSource" name="jdbc/LocalTestDB" global="jdbc/TestDB" /> <Context>
然后從剛配置好的連接池中獲得連接,簡單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 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è)置連接池
去了解這些屬性是很重要的,它們看起來很明顯但又有一些神秘
<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" />
maxActive=100 連接數(shù)據(jù)庫的最大連接數(shù)。這個(gè)屬性用來限制連接池中能夠打開連接的數(shù)量,可以方便數(shù)據(jù)庫做連接容量規(guī)劃。
minIdle=10 連接池中存在的最小連接數(shù)目。連接池中連接數(shù)目可以變很少,如果使用了maxAge屬性,有些空閑的連接會被關(guān)閉因?yàn)殡x它最近一次連接的時(shí)間過去太久了。但是,我們看到的打開的連接不會少于minIdle。
maxIdle屬性有一點(diǎn)麻煩。它的不同的行為取決于是否使用了pool sweeper。pool sweeper是一個(gè)可以在連接池正在使用的時(shí)候測試空閑連接和重置連接池大小的后臺線程。還負(fù)責(zé)檢測連接泄露。pool sweeper 通過如下方式定義的:
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; }
maxIdle定義如下
在這種場景下,如果我們設(shè)置maxIdle=50,那么我們會關(guān)閉和打開50*3的連接數(shù)。這樣增加了數(shù)據(jù)庫的負(fù)重并且減慢了應(yīng)用的速度。當(dāng)達(dá)到連接高峰時(shí),我們希望能夠充分利用連接池中的所有連接。因此,我們強(qiáng)烈希望打開pool sweeper 。我們將在下一個(gè)部分探討具體的事項(xiàng)。我們在這里額外說明maxAge這個(gè)屬性。maxAge定義連接能夠打開或者存在的時(shí)間,單位為毫秒。當(dāng)一個(gè)連接返回到了連接池,如果這個(gè)連接已經(jīng)使用過,并且距離它第一次被使用的時(shí)間大于maxAge時(shí),這個(gè)連接會被關(guān)閉。
正如我們所看到的 isPoolSweeper算法實(shí)現(xiàn),sweeper 將會被打開,當(dāng)以下任一條件滿足時(shí)
As of version 1.0.9 the following condition has been added
(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ù)庫連接池提出了一個(gè)挑戰(zhàn),因?yàn)檫B接池中的連接會過時(shí)。這是常有的事,要么數(shù)據(jù)庫,或者可能是連接池和數(shù)據(jù)庫中的一個(gè)設(shè)備,連接超時(shí)。唯一確定會話連接是活躍的真正辦法是使連接在服務(wù)器和數(shù)據(jù)庫做一個(gè)來回訪問。在Java 6中,JDBC API處理驗(yàn)證連接是否是有效的方法是通過提供isValid變量來調(diào)用java.sql.Connection接口。在此之前,連接池不得不采用執(zhí)行一個(gè)查詢的方法,比如在MySQL上執(zhí)行SELECT 1.數(shù)據(jù)庫分析這句查詢很簡單,不需要任何的磁盤訪問。isValid被計(jì)劃實(shí)施,但 Apache Tomcat 6的連接池,也必須保存對Java 5的兼容性。
校驗(yàn)查詢會有一些挑戰(zhà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" testOnBorrow="true" validationQuery="SELECT 1" />
這樣保證了在連接提交給應(yīng)用之前都已經(jīng)測試過了。但是,對于在短時(shí)間內(nèi)頻繁使用連接的應(yīng)用,會對性能有嚴(yán)重的影響。這有兩個(gè)其他的配置選項(xiàng):
當(dāng)在錯(cuò)誤的時(shí)間對連接做測試,它們也不是真正的很有幫助。
對于很多應(yīng)用來說,沒有校驗(yàn)不是一個(gè)真正的困難。一些應(yīng)用可以繞過校驗(yàn)通過設(shè)置minIdle=0和給minEvictableIdleTimeMillis一個(gè)很小的值,所以如果連接空閑了足夠長的時(shí)間會讓數(shù)據(jù)庫會話超時(shí),在此之前連接池將會移除這些空閑太久的連接。
最好的解決辦法就是測試那些有一段時(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" testOnBorrow="true" validationQuery="SELECT 1" validationInterval="30000" />
<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" />
在一些案例中,當(dāng)初始化一個(gè)新的數(shù)據(jù)庫會話時(shí)需要執(zhí)行一些任務(wù)。可能包括執(zhí)行一個(gè)簡單的SQL聲明或者執(zhí)行一個(gè)存儲過程。當(dāng)你創(chuàng)建觸發(fā)器時(shí)候,這是在數(shù)據(jù)庫層面上的典型操作。
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; /
<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'"/>
連接池包含一些診斷操作。jdbc-pool和Common DBCP都能夠檢測和減輕沒有返回連接池中的連接。這里演示是被稱為拋出內(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" />
但我們想要這種類型的診斷,當(dāng)然有可以使用的例子。也可以運(yùn)行批處理作業(yè)一次執(zhí)行一個(gè)連接幾分鐘。我們該如何處理這些問題?兩個(gè)額外的選項(xiàng)已經(jīng)被加入來支持這些工作
<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" />
使用這個(gè)屬性可能會在一次錯(cuò)誤判斷中產(chǎn)生在其他地方已經(jīng)被認(rèn)為丟棄的連接。設(shè)置這個(gè)值為100時(shí)意味著連接數(shù)除非到了maxActive限制時(shí),是不會被考慮丟棄的。這給連接池增加了一些靈活性,但是不會讓批處理作業(yè)使用單獨(dú)連接5分鐘。在這種情況,我們想確定當(dāng)我們檢測到連接仍然被使用時(shí),我們重置超時(shí)計(jì)時(shí)器,因此,連接不會被考慮丟棄。我們通過插入一個(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" />
這是你當(dāng)然想知道的情形,但你不會想去kill或者回收連接,因?yàn)槟悴粫罆δ愕南到y(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" />
到目前為止我們處理連接池連接的獲得是通過java.sql.Driver接口。因此我們使用屬性
然而,一些連接配置是使用 javax.sql.DataSource 甚至是javax.sql.XADataSource接口,因此我們需要支持這些配置選項(xiàng)。 使用java相對是很容易的。
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();
在XML配置中,jdbc-pool會使用org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory類,一個(gè)能夠允許配置任何類型的命名資源的簡單類。為了設(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è)數(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"/>
目前JNDI通過DataSource.setDataSourceJNDI(...)查找不被支持,只能通過factory對象。
如果你加入一個(gè)
這是一個(gè)有趣的現(xiàn)象當(dāng)你處理 XADataSources。你可以把返回的對象轉(zhuǎn)換為java.sql.Connection對象或者javax.sql.XAConnection對象,并且對同一個(gè)對象的兩個(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 攔截器創(chuàng)建是為了實(shí)現(xiàn)靈活性。javax.sql.PooledConnection 從底層驅(qū)動封裝了java.sql.Connection/javax.sql.XAConnection或者數(shù)據(jù)源本身就是一個(gè)攔截器。攔截器以java.lang.reflect.InvocationHandler接口為基礎(chǔ)。攔截器是一個(gè)繼承自org.apache.tomcat.pool.jdbc.JdbcInterceptor的類。
在本文中,我們將介紹如果配置攔截器。在我們下一篇文章,我們將介紹如果實(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è)完全限定名稱。
攔截器定義在以;分割的字符串中。攔截器可以在括號內(nèi)定義0個(gè)或多個(gè)參數(shù)。參數(shù)是以逗號分割的簡單鍵值對。
java.sql.Connection接口有如下屬性
這些屬性的默認(rèn)值可以使用如下的內(nèi)容為連接池配置
如果設(shè)置了這些屬性,當(dāng)建立連接到數(shù)據(jù)庫時(shí)配置這個(gè)連接。如果沒有配置 ConnectionState攔截器,在建立連接時(shí)設(shè)置這些屬性會是一次性操作。如果配置了ConnectionState攔截器,每次從連接池取出的連接會將被重置為期望的狀態(tài)。
其中有些方法在執(zhí)行查詢時(shí)會導(dǎo)致往返數(shù)據(jù)庫。比如,調(diào)用 Connection.getTransactionIsolation()會導(dǎo)致驅(qū)動查詢當(dāng)前會話的事務(wù)隔離級別。這種往返會導(dǎo)致嚴(yán)重的性能問題并影響應(yīng)用在頻繁的使用連接執(zhí)行非常短和快的操作的時(shí)候。 ConnectionState 攔截器可以緩存這些操作的值并調(diào)用方法查詢它們從而避免往返數(shù)據(jù)庫。
java代碼在使用java.sql對象后需要清除和釋放使用過的資源。
一個(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和它的子類正確關(guān)閉。
使用javax.sql.PooledConnection工具返回代理連接,因此取出連接十分直接,不需要轉(zhuǎn)換為特殊的類。
同樣適用于你配置了處理javax.sql.XAConnection的連接池。
另一個(gè)有趣的取出底層連接的方法是
Connection con = ds.getConnection(); Connection underlyingconnection = con.createStatement().getConnection();
Powered by: BlogJava Copyright © 張金鵬