掘金 后端 ( ) • 2024-04-19 17:15

前言

整理这个官方翻译的系列,原因是网上大部分的 tomcat 版本比较旧,此版本为 v11 最新的版本。

开源项目

从零手写实现 tomcat minicat 别称【嗅虎】心有猛虎,轻嗅蔷薇。

系列文章

web server apache tomcat11-01-官方文档入门介绍

web server apache tomcat11-02-setup 启动

web server apache tomcat11-03-deploy 如何部署

web server apache tomcat11-04-manager 如何管理?

web server apache tomcat11-06-Host Manager App -- Text Interface

web server apache tomcat11-07-Realm Configuration

web server apache tomcat11-08-JNDI Resources

web server apache tomcat11-09-JNDI Datasource

web server apache tomcat11-10-Class Loader

...

介绍

JNDI 数据源配置在 JNDI-Resources-HOWTO 中有详细说明。然而,来自 tomcat-user 的反馈显示,针对特定配置的具体细节可能会相当棘手。

以下是一些已发布到 tomcat-user 的针对流行数据库的示例配置,以及一些通用数据库使用的一般提示。

您应该注意,由于这些注释是从发布到 tomcat-user 的配置和/或反馈中派生的,因此可能会因人而异 :-)。如果您有任何其他经过测试的配置,您认为可能对更广泛的受众有用,或者如果您认为我们可以以任何方式改进此部分,请告诉我们。

请注意,Tomcat 7.x 和 Tomcat 8.x 之间的 JNDI 资源配置在一定程度上有所变化,因为它们使用了不同版本的 Apache Commons DBCP 库。您很可能需要修改旧的 JNDI 资源配置以匹配下面示例中的语法,以使它们在 Tomcat 11 中正常工作。有关详细信息,请参阅 Tomcat 迁移指南。

此外,请注意,JNDI DataSource 配置一般以及本教程特别假设您已经阅读并理解了 Context 和 Host 配置参考,包括后者参考中关于自动应用程序部署的部分。

DriverManager、服务提供程序机制和内存泄漏

java.sql.DriverManager 支持服务提供程序机制。这个特性是,通过提供 META-INF/services/java.sql.Driver 文件宣布自己的所有可用 JDBC 驱动程序会自动被发现、加载和注册,从而使您无需在创建 JDBC 连接之前显式加载数据库驱动程序。然而,在所有 Java 版本中,该实现在 servlet 容器环境中基本上是有缺陷的。问题在于,java.sql.DriverManager 仅会扫描驱动程序一次。

Apache Tomcat 包含的 JRE 内存泄漏预防监听器通过在 Tomcat 启动期间触发驱动程序扫描来解决这个问题。这是默认启用的。这意味着只有可见于公共类加载器及其父加载器的库将被扫描以寻找数据库驱动程序。这包括 $CATALINA_HOME/lib、$CATALINA_BASE/lib、类路径和模块路径中的驱动程序。打包在 Web 应用程序中(在 WEB-INF/lib 中)和共享类加载器中(如果已配置)的驱动程序将不可见,并且不会自动加载。如果您考虑禁用此功能,请注意,扫描将由使用 JDBC 的第一个 Web 应用程序触发,导致在重新加载此 Web 应用程序时以及依赖于此功能的其他 Web 应用程序时失败。

因此,在其 WEB-INF/lib 目录中具有数据库驱动程序的 Web 应用程序不能依赖于服务提供程序机制,并且应该显式注册驱动程序。

java.sql.DriverManager 中的驱动程序列表也是已知的内存泄漏源。由 Web 应用程序注册的任何驱动程序在 Web 应用程序停止时必须取消注册。当 Web 应用程序停止时,Tomcat 将尝试自动发现并注销由 Web 应用程序类加载器加载的任何 JDBC 驱动程序。但是,预期应用程序通过 ServletContextListener 自行执行此操作。

数据库连接池(DBCP 2)配置

Apache Tomcat 中默认的数据库连接池实现依赖于 Apache Commons 项目的库。使用以下库:

  • Commons DBCP 2
  • Commons Pool 2

这些库位于一个单独的 JAR 中,路径为 $CATALINA_HOME/lib/tomcat-dbcp.jar。但是,仅包含了用于连接池的类,并且已经重命名了包以避免干扰应用程序。

DBCP 2 支持 JDBC 4.1。

安装

查看 DBCP 2 文档以获取完整的配置参数列表。

预防数据库连接池泄漏

数据库连接池创建和管理到数据库的连接池。重新使用已经存在的连接比打开新连接更高效。

连接池存在一个问题。Web 应用程序必须显式关闭 ResultSet、Statement 和 Connection。如果 Web 应用程序未关闭这些资源,它们可能永远不会再次可用,从而导致数据库连接池“泄漏”。如果没有更多可用的连接,则最终可能会导致您的 Web 应用程序数据库连接失败。

有一个解决方案。Apache Commons DBCP 2 可以配置为跟踪和恢复这些被遗弃的数据库连接。它不仅可以恢复它们,还可以为打开这些资源但从未关闭它们的代码生成堆栈跟踪。

要配置 DBCP 2 DataSource,以便移除和重新使用被遗弃的数据库连接,请在 DBCP 2 DataSource 的 Resource 配置中添加以下一个或两个属性:

  • removeAbandonedOnBorrow=true
  • removeAbandonedOnMaintenance=true

这两个属性的默认值都是 false。请注意,只有当设置 timeBetweenEvictionRunsMillis 为正值时,removeAbandonedOnMaintenance 属性才会生效。

有关这些属性的详细文档,请参阅 DBCP 2 文档。

使用 removeAbandonedTimeout 属性设置数据库连接在被视为被遗弃之前空闲的秒数。

removeAbandonedTimeout="60"

移除被遗弃连接的默认超时时间为 300 秒。

如果希望

DBCP 2 记录已遗弃数据库连接资源的代码的堆栈跟踪,则可以将 logAbandoned 属性设置为 true。

logAbandoned="true"

默认值为 false。

MySQL DBCP 2 示例

0. 介绍

以下是已知可以工作的 MySQL 和 JDBC 驱动程序的版本:

  • MySQL 3.23.47、MySQL 3.23.47 使用 InnoDB、MySQL 3.23.58、MySQL 4.0.1alpha
  • 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>

上下文配置

在 Tomcat 中配置 JNDI DataSource,通过在 Context 中添加资源声明来实现。

例如:

<Context>

    <!-- maxTotal: 数据库连接池中的最大连接数。确保你配置了足够大的 mysqld max_connections 来处理所有的数据库连接。设置为 -1 表示无限制。 -->

    <!-- maxIdle: 连接池中保留的最大空闲数据库连接数。设置为 -1 表示无限制。请参阅 DBCP 2 文档以了解有关此参数以及 minEvictableIdleTimeMillis 配置参数的更多信息。 -->

    <!-- maxWaitMillis: 等待数据库连接可用的最长时间(以毫秒为单位),例如在此示例中为 10 秒。如果超过此超时时间,则会抛出异常。设置为 -1 表示无限等待。 -->

    <!-- username 和 password: 数据库连接的 MySQL 用户名和密码  -->

    <!-- driverClassName: 旧的 mm.mysql JDBC 驱动程序的类名是 org.gjt.mm.mysql.Driver - 我们建议使用 Connector/J。官方 MySQL Connector/J 驱动程序的类名是 com.mysql.jdbc.Driver。 -->

    <!-- url: 连接到 MySQL 数据库的 JDBC 连接 URL。 -->

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

web.xml 配置

现在为此测试应用程序创建一个 WEB-INF/web.xml。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                      https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
  version="6.0">
  <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 的 SQL 和 Core 标签库。您可以从 Apache Tomcat Taglibs - Standard Tag Library 项目中获取它 — 确保获取 1.1.x 或更新的版本。一旦您获得了 JSTL,将 jstl.jar 和 standard.jar 复制到您的 Web 应用的 WEB-INF/lib 目录中。

最后,将您的 Web 应用部署到 $CATALINA_BASE/webapps,可以是一个名为 DBTest.war 的 war 文件,也可以是一个名为 DBTest 的子目录。

部署后,将浏览器指向 http://localhost:8080/DBTest/test.jsp,即可查看您的辛勤工作的成果。