吾非文人,乃市井一俗人也,读百卷书,跨江河千里,故申城一游; 一两滴辛酸,三四年学业,五六点粗墨,七八笔买卖,九十道人情。
<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 © 张金鹏