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