概述
JNDI 数据源配置的相关内容已经在 JNDI 资源文档中详细介绍过。但从 Tomcat 用户的反馈意见来看,有些配置的细节问题非常棘手。
针对常用的数据库,我们已经给 Tomcat 用户提供了一些配置范例,以及关于数据库使用的一些通用技巧。本章就将展示这些范例和技巧。
另外,虽然有些注意事项来自于用户所提供的配置和反馈信息,但你可能也有不同的实践。如果经过试验,你发现某些配置可能具有广泛的助益作用,或者你觉得它们会使本章内容更加完善,请务必不吝赐教。
请注意,对比 Tomcat 7.x 和 Tomcat 8.x,JNDI 资源配置多少有些不同,这是因为使用的 Apache Commons DBCP 库的版本不同所致。所以,为了在 Tomcat 8 中使用,你最好修改老版本的 JNDI 资源配置,以便能够匹配下文范例中的格式。详情可参看Tomcat 迁移文档。
另外还要提示的是,一般来说(特别是对于本教程而言),JNDI 数据源配置会假定你已经理解了 Context 与 Host 的配置偏好,其中包括在后者配置偏好中的应用自动部署的相关内容。
DriverManager,服务提供者机制以及内存泄露
java.sql.DriverManager 支持服务提供者机制。这项功能的实际作用在于:对于所有可用的 JDBC 驱动,只要它们声明提供 META-INF/services/java.sql.Driver 文件,就会被自动发现、加载并注册,从而减轻了我们在创建 JDBC 连接之前还需要显式地加载数据库驱动的负担。但在 servlet 容器环境的所有 Java 版本中,却根本没法实现这种功能。问题在于 java.sql.DriverManager 只会扫描一次驱动。
Tomcat 自带的阻止 JRE 内存泄漏侦听器可以在一定程度上解决这个问题,它会在 Tomcat 启动时触发驱动扫描。该侦听器默认是启用的。只有可见于该侦听器的库(比如 $CATALINA_BASE/lib 中的库)才能被数据库驱动所扫描。如果你想禁用该功能,那么一定要记住:首先使用 JDBC 的 Web 应用会触发扫描,从而当该应用重新加载时会出错;对于其他依赖该功能的 Web 应用来说也会导致出错。
所以,假如应用的 WEB-INF/lib 目录中存在数据库驱动,那么这些应用就不能依赖服务提供者机制,而应该显式地注册驱动。
java.sql.DriverManager 中的驱动已经被认为是内存泄露之源。当 Web 应用停止运行时,它所注册的任何驱动都必须重新注册。当 Web 应用停止运行时,Tomcat 会尝试自动寻找并重新注册任何由 Web 应用类加载器所加载的 JDBC 驱动。但最好是由应用通过 ServletContextListener 来实现这一点。
数据库连接池(DBCP 2)配置
Apache Tomcat 的默认数据库连接池实现基于的是 Apache Commons 项目的库,具体来说是这两个库:
Commons DBCP
Commons Pool
这两个库都位于一个 JAR 文件中:$CATALINA_HOME/lib/tomcat-dbcp.jar。但该文件只包括连接池所需要的类,包名也已经改变了,以避免与应用冲突。
DBCP 2.0 支持 JDBC 4.1。
安装
可参阅 DBCP 文档了解完整的配置参数。
防止数据库连接池泄露
数据库连接池创建并管理着一些与数据库的连接。与打开新的连接相比,回收或重用现有的数据库连接要更为高效一些。
连接池化还存在一个问题。Web 应用必须明确地关闭 ResultSet、Statement,以及 Connection。假如 Web 应用无法关闭这些资源时,会导致这些资源再也无法被重用,从而造成了数据库连接池“泄露”。如果再也没有可用连接时,最终这将导致 Web 应用数据库连接失败。
针对该问题,有一个解决办法:通过配置 Apache Commons DBCP,记录并恢复这些废弃的数据库连接。它不仅能恢复这些连接,而且还能针对打开这些连接而又永远不关闭它们的代码生成堆栈跟踪。
为了配置 DBCP 数据源来移除并回收废弃的数据库连接,将下列属性(一个或全部)添加到你的 DBCP 数据源中的 Resource 配置中:
removeAbandonedOnBorrow=true
removeAbandonedOnMaintenance=true
以上属性默认都为 false。注意,只有当 timeBetweenEvictionRunsMillis 为正值,从而启用池维护时,removeAbandonedOnMaintenance 才能生效。关于这些属性的详情,可查看 DBCP 文档 。
使用 removeAbandonedTimeout 属性设置某个数据库连接闲置的秒数,超过此时段,即被认为是废弃连接。
removeAbandonedTimeout="60"
默认的去除废弃连接的超时为 300 秒。
将 logAbandoned 设为 true,可以让 DBCP 针对那些抛弃数据库连接资源的代码,记录堆栈跟踪信息。
logAbandoned="true"
默认为 false。
MySQL DBCP 范例
0. 简介
已报告的能够正常运作的 MySQL 与 JDBC 驱动的版本号为:
MySQL 3.23.47、使用 InnoDB 的 MySQL 3.23.47、MySQL 3.23.58 以及 MySQL 4.0.1 alpha
Connector/J 3.0.11-stable (JDBC 官方驱动)
mm.mysql 2.0.14 (一个较老的 JDBC 第三方驱动)
在继续下一步的操作之前,千万不要忘了将 JDBC 驱动的 JAR 文件复制到 $CATALINA_HOME/lib 中。
1. 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>
2. 上下文配置
在 Context 中添加资源声明,以便在 Tomcat 中配置 JNDI 数据源。
范例如下:
<Context> <!-- maxTotal: Maximum number of database connections in pool. Make sure you configure your mysqld max_connections large enough to handle all of your db connections. Set to -1 for no limit. --> <!-- maxIdle: Maximum number of idle database connections to retain in pool. Set to -1 for no limit. See also the DBCP documentation on this and the minEvictableIdleTimeMillis configuration parameter. --> <!-- maxWaitMillis: Maximum time to wait for a database 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 username and password for database 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 database. --> <Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource" maxTotal="100" maxIdle="30" maxWaitMillis="10000" username="javauser" password="javadude" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/javatest"/> </Context>
3. 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"> <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>
4. 测试代码
创建一个简单的 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 的 SQL 和 Core taglibs。你可以从 Apache Tomcat Taglibs - Standard Tag Library 项目中获取它,不过要注意应该是 1.1.x 或之后的版本。下载 JSTL 后,将 jstl.jar 和 standard.jar 复制到 Web 应用的 WEB-INF/lib 目录中。
最后,将你的应用部署到 $CATALINA_BASE/webapps,可以采用两种方式:或者将应用以名叫 DBTest.war 的 WAR 文件形式部署;或者把应用放入一个叫 DBTest 的子目录中。
部署完毕后,就可以在浏览器输入 http://localhost:8080/DBTest/test.jsp,查看你的第一个劳动成果了。
由于篇幅过长,本章未完待续!
感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程