Tomcat 5.5.23 文档阅读Tips 8 - JNDI Datasource
本文特别对Datasource类型的JNDI的配置做了解说。
1. Tomcat对DataSource类型的资源采用的是DBCP。DBCP支持JDBC 2.0, 在安装了JVM 1.4版本或更高的情况下,DBCP支持JDBC 3.0。
2. DBCP需要以下三个component,他们都来自Apache Jakarta Common Project,他们是:
Jakarta-Commons DBCP
Jakarta-Commons Collections
Jakarta-Commons Pool
这三个component都被包装在了$CATALINA_HOME/common/lib/naming-factory-dbcp.jar这个jar文件中。
3. Preventing DB Pool leaks. 这个很不错,DBCP为了防止应用程序忘记关闭ResultSets,Statements, Connections,特意设计了这样一个功能。DBCP能帮我们关闭不活动的connections,而且还能打印出stack trace,报告说是哪个class使用了connection,却没有关闭它。具体配置如下:
在定义DataSource的Resource定义中,加入attribute定义:
removeAbandoned="true"
这个属性默认配置为false。这样DBCP就可以关闭那些可能被应用程序忘记关闭的connection,具体的timeout设定这样配置:
removeAbandonedTimeout="60"
也就是说,如果DBCP发现一个connection 60秒内处于不活动状态,DBCP便自动回收。这个配置的默认值是300秒,也就是5分钟。
此外,还可以加入这个属性定义:
logAbandoned="true"
这样DBCP就会打印stack trace,让我们看到是哪个class忘记关闭connection。这个属性默认配置为false。
1. Tomcat对DataSource类型的资源采用的是DBCP。DBCP支持JDBC 2.0, 在安装了JVM 1.4版本或更高的情况下,DBCP支持JDBC 3.0。
2. DBCP需要以下三个component,他们都来自Apache Jakarta Common Project,他们是:
Jakarta-Commons DBCP
Jakarta-Commons Collections
Jakarta-Commons Pool
这三个component都被包装在了$CATALINA_HOME/common/lib/naming-factory-dbcp.jar这个jar文件中。
3. Preventing DB Pool leaks. 这个很不错,DBCP为了防止应用程序忘记关闭ResultSets,Statements, Connections,特意设计了这样一个功能。DBCP能帮我们关闭不活动的connections,而且还能打印出stack trace,报告说是哪个class使用了connection,却没有关闭它。具体配置如下:
在定义DataSource的Resource定义中,加入attribute定义:
removeAbandoned="true"
这个属性默认配置为false。这样DBCP就可以关闭那些可能被应用程序忘记关闭的connection,具体的timeout设定这样配置:
removeAbandonedTimeout="60"
也就是说,如果DBCP发现一个connection 60秒内处于不活动状态,DBCP便自动回收。这个配置的默认值是300秒,也就是5分钟。
此外,还可以加入这个属性定义:
logAbandoned="true"
这样DBCP就会打印stack trace,让我们看到是哪个class忘记关闭connection。这个属性默认配置为false。
4. 文档给出了一个MySQL DataSource配置的例子。
(1) 将mysql jdbc的jar包放到$CATALINA_HOME/common/lib目录下。目前MySQL提供的官方驱动是Connector/J,目前EasyCluster中使用的是Connector/J 3.1.12版本,可以稳定工作。
(2) 在Tomcat中配置DataSource资源,以下例子给出的是在Context中的配置,也就说,只能给这个web app使用:
(3) 配置web.xml
(4) OK, 书写测试代码:
(1) 将mysql jdbc的jar包放到$CATALINA_HOME/common/lib目录下。目前MySQL提供的官方驱动是Connector/J,目前EasyCluster中使用的是Connector/J 3.1.12版本,可以稳定工作。
(2) 在Tomcat中配置DataSource资源,以下例子给出的是在Context中的配置,也就说,只能给这个web app使用:
- CODE: SELECT ALL
<Context path="/DBTest" docBase="DBTest"
debug="5" reloadable="true" crossContext="true">
<!-- maxActive: Maximum number of dB connections in pool. Make sure you
configure your mysqld max_connections large enough to handle
all of your db connections. Set to 0 for no limit.
-->
<!-- maxIdle: Maximum number of idle dB connections to retain in pool.
Set to -1 for no limit. See also the DBCP documentation on this
and the minEvictableIdleTimeMillis configuration parameter.
-->
<!-- maxWait: Maximum time to wait for a dB connection to become available
in ms, in this example 10 seconds. An Exception is thrown if
this timeout is exceeded. Set to -1 to wait indefinitely.
-->
<!-- username and password: MySQL dB username and password for dB connections -->
<!-- driverClassName: Class name for the old mm.mysql JDBC driver is
org.gjt.mm.mysql.Driver - we recommend using Connector/J though.
Class name for the official MySQL Connector/J driver is com.mysql.jdbc.Driver.
-->
<!-- url: The JDBC connection url for connecting to your MySQL dB.
The autoReconnect=true argument to the url makes sure that the
mm.mysql JDBC Driver will automatically reconnect if mysqld closed the
connection. mysqld by default closes idle connections after 8 hours.
-->
<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="javauser" password="javadude" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/javatest?autoReconnect=true"/>
</Context>
(3) 配置web.xml
- CODE: SELECT ALL
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/TestDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
(4) OK, 书写测试代码:
- CODE: SELECT ALL
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<sql:query var="rs" dataSource="jdbc/TestDB">
select id, foo, bar from testdata
</sql:query>
<html>
<head>
<title>DB Test</title>
</head>
<body>
<h2>Results</h2>
<c:forEach var="row" items="${rs.rows}">
Foo ${row.foo}<br/>
Bar ${row.bar}<br/>
</c:forEach>
</body>
</html>
5. Common Problems I. 第一个常见问题就是“间歇性的取connection失败”。Tomcat分析说这是由于GC导致的。也就说,当GC发生的时候,Tomcat处于停止状态,如果GC花费的时间过长,长到超过了DBCP中配置的maxWait的时间,这种问题就会出现。DBCP中的maxWait表示的是当超过这个时间后,DBCP就认为取connection失败,这个值用毫秒表示。加入GC花费了15秒的时间(当然一般GC如果花费了10s就表示已经很恐怖了),而maxWait设定成10s的话,就会出现上面的问题了。解决方案是启动tomcat的时候加上-verbose:gc参数,观察每次gc花费的时间,然后微调maxWait或其他东西。
6. Common Problems II. Random Connection Close Exception. 这种问题EasyCluster中没有,他是这样产生的:
(1) 请求1请求了一个连接,用完后调用了close,此时JVM切换线程到请求2
(2) 请求2请求了一个连接,由于请求1已经用完了上一个连接,于是DBCP将请求1对应的connection给请求2使用。请求2还没开始使用,JVM切换线程回到请求1
(3) 在请求1的finally代码块中,又一次close了该connection,此时JVM调度线程回到请求2
(4) 请求2开始使用该connection,发生异常。
解决方法很简单,就是在正常代码中close connection之后随即将该connection设成null,然后在finally代码块中首先判断connection是否为null,不为null再close。加了这些判断之后,就避免了某个线程误close connection的现象发生。
以下是文档给出的示例代码:
6. Common Problems II. Random Connection Close Exception. 这种问题EasyCluster中没有,他是这样产生的:
(1) 请求1请求了一个连接,用完后调用了close,此时JVM切换线程到请求2
(2) 请求2请求了一个连接,由于请求1已经用完了上一个连接,于是DBCP将请求1对应的connection给请求2使用。请求2还没开始使用,JVM切换线程回到请求1
(3) 在请求1的finally代码块中,又一次close了该connection,此时JVM调度线程回到请求2
(4) 请求2开始使用该connection,发生异常。
解决方法很简单,就是在正常代码中close connection之后随即将该connection设成null,然后在finally代码块中首先判断connection是否为null,不为null再close。加了这些判断之后,就避免了某个线程误close connection的现象发生。
以下是文档给出的示例代码:
- CODE: SELECT ALL
Connection conn = null;
Statement stmt = null; // Or PreparedStatement if needed
ResultSet rs = null;
try {
conn = ... get connection from connection pool ...
stmt = conn.createStatement("select ...");
rs = stmt.executeQuery();
... iterate through the result set ...
rs.close();
rs = null;
stmt.close();
stmt = null;
conn.close(); // Return to connection pool
conn = null; // Make sure we don't close it twice
} catch (SQLException e) {
... deal with errors ...
} finally {
// Always make sure result sets and statements are closed,
// and the connection is returned to the pool
if (rs != null) {
try { rs.close(); } catch (SQLException e) { ; }
rs = null;
}
if (stmt != null) {
try { stmt.close(); } catch (SQLException e) { ; }
stmt = null;
}
if (conn != null) {
try { conn.close(); } catch (SQLException e) { ; }
conn = null;
}
}