The Jakarta Project
      The Apache Jakarta Tomcat Servlet/JSP Container

链接

用户指南

参考

开发 Tomcat

The Apache Jakarta Tomcat 5 Servlet/JSP Container

JNDI Datasource HOW-TO

Printer Friendly Version
print-friendly
版本
目录

简介
数据库连接池(DBCP)配置
非DBCP解决办法
Oracle 8i with OCI client
常见问题

简介

JNDI数据源配置在JNDI-Resources-HOWTO里面有详尽的说明,tomcat用户反馈回来的信息显示个别的特殊配置可能会很易出错。

这里为tomcat用户展示了一些通用数据库配置的例子,以及数据库使用的一些通常的提示。

因为这些注解是从tomcat用户反馈的配置信息派生而来,所以如果你有其他测试过的配置并且觉得能对更多其他用户会有用处,或者你觉得我们可以对这一部分有所改进,请让我们知道。

数据库连接池(DBCP)配置

DBCP对JDBC 2.0提供支持。 使用1.4 JVM DBCP的系统会支持JDBC 3.0。如果你已经使用过1.4 JVM DBCP以及JDBC 3.0特性,请让我们知道。

关于配置参数的全部列单,请参看DBCP Javadocs BasicDataSource 类。

安装

DBCP使用Jakarta-Commons数据库连接池。它依赖于一组Jakarta-Commons部件:

  • Jakarta-Commons DBCP 1.0
  • Jakarta-Commons Collections 2.0
  • Jakarta-Commons Pool 1.0
这些jar文件与你的JDBC驱动的jar文件一起应该被安装在$CATALINA_HOME/common/lib里边。
注意:第三类的驱动必须是以jarfiles形式存在,而不是zipfiles。Tomcat只把$CATALINA_HOME/common/lib/*.jar 加入到classpath上。
注意:不要把这些jarfiles安装在/WEB-INF/lib,或$JAVA_HOME/jre/lib/ext,或其他任何地方。如果你不是把它们安装在$CATALINA_HOME/common/lib,而是安装在其他任何地方,你就会遇到问题。

防止数据库连接池泄露

一个数据库连接池产生并管理一系列与数据库的连接。再回收和再利用现存的数据库连接比打开一个新的连接要有效率的多。

连接池也有一个问题。那就是网络程序必须专门关闭ResultSet's, Statement's, 和 Connection's 。 网络程序若没有关闭这些资源,会造成这些资源再也不能被重新使用,产生数据库连接池“泄露”的后果。如果没有别的连接可被使用,最终会造成你的网络程序数据库连接失败。

对于这个问题有一个解决办法。Jakarta-Commons DBCP可以被配置来追踪和修复这些被遗弃的数据库连接。它不仅可以修复它们,而且可以对那些打开这些资源但又从来没有关闭它们的代码展开一层层追踪。

要配置DBCP DataSource让被放弃的数据库连接被删除,然后再回收,需要把下面的参数添加到你的DBCP数据源资源ResourceParams配置里面。

<parameter> 
<name>removeAbandoned</name> 
<value>true</value> 
</parameter>
当可被利用的数据库连接降到很低,DBCP会修复并回收它能发现的被放弃的数据库连接。默认值是false

使用removeAbandonedTimeout参数来设定数据库连接在被放弃之前闲置的秒数。

<parameter> 
<name>removeAbandonedTimeout</name> 
<value>60</value> 
</parameter>
删除被放弃的连接的默认时间是300秒。

如果你想要DBCP日志(log)住对于那些放弃数据库连接资源的代码的层层追踪,logAbandoned参数可以被设置为true

<parameter> 
<name>logAbandoned</name> 
<value>true</value> 
</parameter>
默认值是false

MySQL DBCP 示例

简介

下面这些的MySQL和JDBC驱动的版本可以运作:

  • MySQL 3.23.47, MySQL 3.23.47 using InnoDB,, MySQL 3.23.58, MySQL 4.0.1alpha
  • Connector/J 3.0.11-stable (the official JDBC Driver)
  • mm.mysql 2.0.14 (an old 3rd party JDBC Driver)

在你开始之前,别忘记复制一份JDBC驱动的jar文件到$CATALINA_HOME/common/lib里面。

MySQL 配置

你一定要遵循这些指导说明,因为变动会产生问题。

产生一个新的测试用户,一个新的数据库和一个测试表格。你的MySQL用户必须要有个指派的密码。如果你试图用空密码连接,驱动会失败。

mysql> GRANT ALL PRIVILEGES ON *.* TO javauser@localhost 
-> IDENTIFIED BY 'javadude' WITH GRANT OPTION; 
mysql> create database javatest; 
mysql> use javatest; 
mysql> create table testdata ( 
-> id int not null auto_increment primary key, 
-> foo varchar(25), 
-> bar int);
注意:当测试完成以后,上面的用户应该被删除掉。

下一步,在testdata表格中加入一些测试数据。

mysql> insert into testdata values(null, 'hello', 12345); 
Query OK, 1 row affected (0.00 sec) 

mysql> select * from testdata; 
+----+-------+-------+ 
| ID | FOO | BAR | 
+----+-------+-------+ 
| 1 | hello | 12345 | 
+----+-------+-------+ 
1 row in set (0.00 sec) 

mysql>

server.xml 配置

通过向$CATALINA_HOME/conf/server.xml里面加入一个声明来在Tomcat里配置JNDI DataSource。

在例子的</Context>标志与关闭局部主机的</Host>标志之间添加它。

<Context path="/DBTest" docBase="DBTest" 
debug="5" reloadable="true" crossContext="true"> 

<Logger className="org.apache.catalina.logger.FileLogger" 
prefix="localhost_DBTest_log." suffix=".txt" 
timestamp="true"/> 

<Resource name="jdbc/TestDB" 
auth="Container" 
type="javax.sql.DataSource"/> 

<ResourceParams name="jdbc/TestDB"> 
<parameter> 
<name>factory</name> 
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value> 
</parameter> 

<!-- 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. 
--> 
<parameter> 
<name>maxActive</name> 
<value>100</value> 
</parameter> 

<!-- 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. 
--> 
<parameter> 
<name>maxIdle</name> 
<value>30</value> 
</parameter> 

<!-- 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. 
--> 
<parameter> 
<name>maxWait</name> 
<value>10000</value> 
</parameter> 

<!-- MySQL dB username and password for dB connections --> 
<parameter> 
<name>username</name> 
<value>javauser</value> 
</parameter> 
<parameter> 
<name>password</name> 
<value>javadude</value> 
</parameter> 

<!-- Class name for the old mm.mysql JDBC driver - uncomment this entry and comment next 
if you want to use this driver - we recommend using Connector/J though 
<parameter> 
<name>driverClassName</name> 
<value>org.gjt.mm.mysql.Driver</value> 
</parameter> 
--> 

<!-- Class name for the official MySQL Connector/J driver --> 
<parameter> 
<name>driverClassName</name> 
<value>com.mysql.jdbc.Driver</value> 
</parameter> 

<!-- 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. 
--> 
<parameter> 
<name>url</name> 
<value>jdbc:mysql://localhost:3306/javatest?autoReconnect=true</value> 
</parameter> 
</ResourceParams> 
</Context>

web.xml 配置

现在为这个测试程序产生一个WEB-INF/web.xml 文件。

<web-app xmlns="http://java.sun.com/xml/ns/j2ee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" 
version="2.4"> 
<web-app> 
<description>MySQL Test App</description> 
<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> 
</web-app>

测试代码

现在产生一个简单的test.jsp页面供以后使用。

<%@ 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>

JSP页面利用JSTL 's SQL 和 Core taglibs 。你可以从Sun's Java Web Services Developer Pack 得到它,或者从Jakarta Taglib Standard 1.1 project 得到它——确保你拿到的是1.1.x发行版。当你有了JSTL以后,把jstl.jarstandard.jar复制到你的网络程序的WEB-INF/lib目录里。

最后把你的网络程序作为DBTest.war的warfile形式,或以名叫DBTest的子目录形式部署到$CATALINA_HOME/webapps 里面。

当部署完成后,在一个浏览器里指向http://localhost:8080/DBTest/test.jsp就可以看到你辛勤劳动的成果了。

Oracle 8i

简介

因为我不是一个Oracle DBA,非常感谢你能对这一段提出建议:-)

在MySQL配置的基础上,Oracle只要求很少的改变:首先,以默认的方式,Tomcat只使用安装在$CATALINA_HOME/common/lib里的*.jar文件,因此classes111.zipclasses12.zip需要被重新命名为*.jar 扩展符结束的文件。*.jarfiles 是 zipfiles,所以不需要unzi*.jar这些文件——简单的重新命名就足够了。但是,你要注意,一些早期版本的Tomcat 4.0,在使用JDK 1.4时不会装载classes12.zip ,直到你unzip这个文件,删除javax.sql.* 类阶层并且*.jar这个文件。

server.xml 配置

与上面的mysql config相类似,你需要在你的server.xml文件定义你的数据源 。这里我们定义一个叫做myoracle的数据源——通过使用thin驱动来与一个被称为mysid的sid里面的叫做myschema的模式连接,用户名是scott,密码是tiger。(注意:在thin驱动里,这个sid与tnsname不相同)。

使用OCI驱动只需要在URL字串里简单地把thin改变成oci 。

<Resource name="jdbc/myoracle" auth="Container" 
type="javax.sql.DataSource"/> 

<ResourceParams name="jdbc/myoracle"> 
<parameter> 
<name>factory</name> 
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value> 
</parameter> 
<parameter> 
<name>driverClassName</name> 
<value>oracle.jdbc.driver.OracleDriver</value> 
</parameter> 
<parameter> 
<name>url</name> 
<value>jdbc:oracle:thin:myschema@127.0.0.1:1521:mysid</value> 
</parameter> 
<parameter> 
<name>username</name> 
<value>scott</value> 
</parameter> 
<parameter> 
<name>password</name> 
<value>tiger</value> 
</parameter> 
<parameter> 
<name>maxActive</name> 
<value>20</value> 
</parameter> 
<parameter> 
<name>maxIdle</name> 
<value>10</value> 
</parameter> 
<parameter> 
<name>maxWait</name> 
<value>-1</value> 
</parameter> 
</ResourceParams>

web.xml 配置

你应该确保在产生web.xml文件时,尊重DTD定义的元素排列顺序。

<resource-ref> 
<description>Oracle Datasource example</description> 
<res-ref-name>jdbc/myoracle</res-ref-name> 
<res-type>javax.sql.DataSource</res-type> 
<res-auth>Container</res-auth> 
</resource-ref>

代码示例

你可以使用与上面相同的程序例子(假设你产生了必需的数据库实例,表格等)来代替Datasource代码,它可能象这样:

Context initContext = new InitialContext(); 
Context envContext = (Context)initContext.lookup("java:/comp/env"); 
DataSource ds = (DataSource)envContext.lookup("jdbc/myoracle"); 
Connection conn = ds.getConnection(); 
//etc.
PostgreSQL

简介

PostgreSQL是以与Oracle相类似的形式配置的。这里强调了它们的区别。这些说明还没有被测试过,我们非常感谢任何反馈信息。

server.xml 配置

<Resource name="jdbc/postgres" auth="Container" 
type="javax.sql.DataSource"/> 

<ResourceParams name="jdbc/postgres"> 
<parameter> 
<name>factory</name> 
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value> 
</parameter> 
<parameter> 
<name>driverClassName</name> 
<value>org.postgresql.Driver</value> 
</parameter> 
<parameter> 
<name>url</name> 
<value>jdbc:postgresql://127.0.0.1:5432/mydb</value> 
</parameter> 
<parameter> 
<name>username</name> 
<value>myuser</value> 
</parameter> 
<parameter> 
<name>password</name> 
<value>mypasswd</value> 
</parameter> 
<parameter> 
<name>maxActive</name> 
<value>20</value> 
</parameter> 
<parameter> 
<name>maxIdle</name> 
<value>10</value> 
</parameter> 
<parameter> 
<name>maxWait</name> 
<value>-1</value> 
</parameter> 
</ResourceParams>

web.xml 配置

<resource-ref> 
<description>postgreSQL Datasource example</description> 
<res-ref-name>jdbc/postgres</res-ref-name> 
<res-type>javax.sql.DataSource</res-type> 
<res-auth>Container</res-auth> 
</resource-ref>
非DBCP解决办法

这些办法要么是利用单个数据库连接(建议仅作测试用,而不要作其它用处),要么是其他pooling技术。

Oracle 8i with OCI client
简介

虽然没有严格强调使用OCI client来产生JNDI DataSource,这里的这些注解可以和上面所讲的Oracle and DBCP方法合在一起。

为了使用OCI驱动,你必须要安装一个Oracle client。你应该从cd安装Oracle8i(8.1.7) client ,然后再从otn.oracle.com下载相适宜的JDBC/OCI driver(Oracle8i 8.1.7.1 JDBC/OCI Driver)。

在把classes12.zip文件重新命名为classes12.jar以后,把它复制到Tomcat的$CATALINA_HOME/common/lib里面。根据你使用的Tomcat和JDK 版本,你还可能必须从这个文件删除javax.sql.* 类。

Putting it all together

确保在你的$PATH or LD_LIBRARY_PATH (possibly in $ORAHOMEbin ) 有ocijdbc8.dll or .so ,并且确定使用一个简单的测试程序System.loadLibrary("ocijdbc8")可以装载native library。  $PATH

你下一步应该产生一个简单的测试servlet或jsp,要有下面这些关键行

DriverManager.registerDriver(new 
oracle.jdbc.driver.OracleDriver()); 
conn = 
DriverManager.getConnection("jdbc:oracle:oci8:@database","username","password");

这里数据库是host:port:SID的形式。现在如果你想试图访问你测试的servlet/jsp 的URL,你得到的是一个ServletException,造成它的根本原因在于java.lang.UnsatisfiedLinkError:get_env_handle

首先,这个UnsatisfiedLinkError表明你有下列问题:

  • JDBC类文件和Oracle client版本不相配。发给你的消息会说明是因为一个必须的library不能被找到。例如,你可能把Oracle Version 8.1.6里边的一个classes12.zip文件与与Version 8.1.5 Oracle client 一起使用。classeXXXs.zip文件与Oracle client软件的版本必须相配。
  • 一个$PATH , LD_LIBRARY_PATH 问题。  $PATH
  • 据报道,忽略你从otn下载的驱动,使用$ORAHOMEjdbclib目录里的classes12.zip文件也行得通。

下一步,你可能会遇到错误ORA-06401 NETCMN: invalid driver designator

Oracle文件说明会说:"Cause: The login (connect) string contains an invalid driver designator. Action: Correct the string and re-submit." 把数据库的连接字串( host:port:SID 的形式)改变成这个样:(description=(address=(host=myhost)(protocol=tcp)(port=1521))(connect_data=(sid=orcl)))

Ed. Hmm, I don't think this is really needed if you sort out your TNSNames - but I'm not an Oracle DBA :-)

常见问题

这里是一些网络程序中使用数据库常常遇到的问题,以及怎样解决它们的提示。

间歇性数据库连接失败

Tomcat在一个JVM里运行。JVM定期地执行垃圾收集工作(GC)来删除不再被使用的对象。当JVM执行GC时,Tomcat暂停。如果配置的连接数据库的最长时间少于垃圾收集所花费的时间,你就可能得到一个数据库连接失败。

要收集关于垃圾收集所花费的时间,在启动Tomcat时,把-verbose:gc加入到你的CATALINA_OPTS环境变量中。当垃圾收集(gc)工作时,你的$CATALINA_BASE/logs/catalina.out日志文件会记录所有的关于垃圾收集的数据,包括垃圾收集需要多长时间。

当你的JVM调试正确率为99%时,垃圾收集工作花费的时间比一秒钟还少。收尾工作仅仅花几秒钟。垃圾收集很少用10秒钟以上的时间。

确保数据库连接时间设置为10-15秒。对于DBCP来说,用maxWait参数来设置。

随意性连接关闭异常

当一个请求从连接池得到一个数据库连接,然后关闭它两次,就会产生这些异常。当使用一个连接池时,关闭连接仅仅是把它返回到池中再被另外的请求使用,而不时把这个连接断掉。Tomcat使用多线程来处理同时发生的请求。这里的例子是关于事件(events)顺序怎样在Tomcat里造成这类错误:

Request 1 running in Thread 1 gets a db connection. 

Request 1 closes the db connection. 

The JVM switches the running thread to Thread 2 

Request 2 running in Thread 2 gets a db connection 
(the same db connection just closed by Request 1). 

The JVM switches the running thread back to Thread 1 

Request 1 closes the db connection a second time in a finally block. 

The JVM switches the running thread back to Thread 2 

Request 2 Thread 2 tries to use the db connection but fails 
because Request 1 closed it.
这里的例子是一个写得不错的关于使用从连接池获得的数据库连接的代码。
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; 
} 
}

Context 及 GlobalNamingResources

请注意虽然在上面的说明中把JNDI声明放在一个Context元素里面,有可能,而且有时更需要把这些声明放在服务器配置文件的GlobalNamingResources区域。被放置在GlobalNamingResources区域的资源将会被服务器的Contexts共享。

JNDI资源命名和Realm互动

为了让Realms能运作,这个realm必须按照<GlobalNamingResources> 或 <Context> 章节定义的那样指向数据源,而不是指向用<ResourceLink>重新命名的数据源。


Copyright © 1999-2003, Apache Software Foundation