杰瑞科技汇

Java Tomcat连接池如何高效配置?

  1. 什么是连接池?为什么需要它?
  2. Tomcat 内置的连接池:Tomcat JDBC Pool
  3. 如何配置和使用 Tomcat JDBC Pool
  4. 主流第三方连接池(Druid, HikariCP)与 Tomcat 的集成
  5. 最佳实践和注意事项

什么是连接池?为什么需要它?

问题背景

在传统的数据库操作中,每次需要与数据库交互时,应用程序都需要执行以下步骤:

Java Tomcat连接池如何高效配置?-图1
(图片来源网络,侵删)
  1. 加载驱动 (虽然现代 JDBC 驱动会自动处理)
  2. 建立连接 (DriverManager.getConnection())
  3. 执行 SQL
  4. 关闭连接 (connection.close())

这个过程非常耗时,尤其是“建立连接”这一步,涉及到网络通信和数据库服务端的认证、资源分配等,对于一个高并发的 Web 应用(如 Tomcat 托管的网站),如果每个请求都创建和销毁一个连接,性能将会急剧下降,数据库也可能因为连接数耗尽而崩溃。

解决方案:连接池

数据库连接池是一个预先创建和管理好多个数据库连接的容器,当应用程序需要一个连接时,它不是去创建一个新的,而是从连接池中“借用”一个,使用完毕后,也不是真的关闭,而是“归还”给连接池,以供下一个请求使用。

连接池带来的好处:

  • 性能提升:复用连接,避免了频繁创建和销毁连接的开销。
  • 资源控制:限制了应用对数据库的最大连接数,防止因连接数过多导致数据库崩溃。
  • 管理方便:可以集中管理连接,监控连接状态,实现连接的自动回收等。

Tomcat 内置的连接池:Tomcat JDBC Pool

从 Tomcat 7.0 开始,官方推荐使用 Tomcat JDBC Pool,它取代了之前基于 Apache Commons DBCP 的旧连接池。

Java Tomcat连接池如何高效配置?-图2
(图片来源网络,侵删)

Tomcat JDBC Pool 的优势

  • 高性能:经过了高度优化,性能非常出色,在某些场景下甚至优于一些第三方连接池。
  • 异步支持:支持异步获取连接,可以充分利用 NIO 提高并发性能。
  • 高可用性:内置连接泄漏检测、连接验证等功能。
  • 与 Tomcat 无缝集成:作为 Tomcat 的一部分,配置和使用非常方便。
  • 兼容 JDBC 4.0+:支持标准的 JDBC API。

如何配置和使用 Tomcat JDBC Pool

配置 Tomcat 连接池主要有两种方式:

  1. context.xml 中配置(推荐):配置与 Web 应用解耦,一个连接池可以被该应用中的所有 Servlet/JSP/DAO 共享,并且可以随 Web 应用一起部署和移除。
  2. web.xml 中配置:这种方式比较老旧,不推荐使用。

下面我们以最推荐的 context.xml 方式为例,并使用 JNDI (Java Naming and Directory Interface) 来查找连接池。

步骤 1:准备数据库驱动

将你的数据库 JDBC 驱动 JAR 包(如 mysql-connector-java-8.x.x.jar)放入 Tomcat 的 lib 目录下,这样 Tomcat 才能识别并加载驱动。

步骤 2:配置 context.xml

在你的 Web 项目的 META-INF 目录下(如果没有,就手动创建一个),创建一个 context.xml 文件。

Java Tomcat连接池如何高效配置?-图3
(图片来源网络,侵删)

META-INF/context.xml 内容示例 (以 MySQL 为例):

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <!-- 定义一个名为 "jdbc/MyAppDB" 的数据源 -->
    <Resource name="jdbc/MyAppDB"
              auth="Container"
              type="javax.sql.DataSource"
              driverClassName="com.mysql.cj.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/your_database_name?useSSL=false&amp;serverTimezone=UTC"
              username="your_username"
              password="your_password"
              maxTotal="100"           <!-- 连接池中最大连接数 -->
              maxIdle="30"             <!-- 连接池中最大空闲连接数 -->
              minIdle="10"             <!-- 连接池中最小空闲连接数 -->
              initialSize="10"         <!-- 初始化时创建的连接数 -->
              maxWaitMillis="10000"    <!-- 当连接池没有可用连接时,等待获取连接的最大时间(毫秒) -->
              validationQuery="SELECT 1" <!-- 用来验证连接是否有效的SQL查询 -->
              testOnBorrow="true"     <!-- 在从池中借出连接时,是否执行validationQuery进行验证 -->
              testOnReturn="false"    <!-- 在将连接归还到池中时,是否执行validationQuery进行验证 -->
              testWhileIdle="true"    <!-- 当连接空闲时,是否执行validationQuery进行验证 -->
              timeBetweenEvictionRunsMillis="30000" <!-- 定时检查空闲连接的间隔时间(毫秒) -->
              minEvictableIdleTimeMillis="60000"    <!-- 连接在池中保持空闲的最小时间,超过此时间可能被回收(毫秒) -->
    />
</Context>

参数解释:

  • name: JNDI 名称,Java 代码中会通过这个名称来查找数据源。
  • auth: 表示由容器(Tomcat)来管理权限。
  • type: 指定资源的类型,这里是 javax.sql.DataSource
  • driverClassName, url, username, password: 数据库连接的基本信息。
  • maxTotal: 连接池的“总容量”,防止过度消耗数据库资源。
  • maxIdle: 连接池中允许存在的最大空闲连接数,如果空闲连接超过这个数,多余的连接会被回收。
  • minIdle: 连接池中始终保持的最小空闲连接数,如果空闲连接数小于这个值,连接池会创建新的连接来补充。
  • initialSize: 连接池启动时初始化的连接数。
  • maxWaitMillis: 当所有连接都在使用中时,新的请求等待获取连接的超时时间,超时后会抛出 SQLException
  • validationQuery: 一条简单的、能快速执行的 SQL,用于检查连接是否仍然有效。SELECT 1SELECT ping()
  • testOnBorrow, testOnReturn, testWhileIdle: 控制何时执行 validationQuery,以检测无效连接(因网络问题或数据库重启导致的断开)。

步骤 3:在 Java 代码中使用 JNDI 查找数据源

你可以在你的 Servlet、DAO 或任何业务逻辑类中通过 JNDI 获取这个连接池。

示例代码:

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserDao {
    private DataSource dataSource;
    // 构造函数中初始化数据源
    public UserDao() {
        try {
            // 1. 创建 JNDI 初始上下文
            Context initContext = new InitialContext();
            // 2. 通过 JNDI 名称查找数据源
            // "java:comp/env" 是 Web 应用中资源的固定前缀
            Context envContext = (Context) initContext.lookup("java:comp/env");
            dataSource = (DataSource) envContext.lookup("jdbc/MyAppDB");
        } catch (Exception e) {
            throw new RuntimeException("Failed to initialize DataSource", e);
        }
    }
    public void findUserById(int id) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        String sql = "SELECT id, username, email FROM users WHERE id = ?";
        try {
            // 3. 从连接池中获取一个连接
            connection = dataSource.getConnection();
            // 4. 使用连接进行数据库操作
            statement = connection.prepareStatement(sql);
            statement.setInt(1, id);
            resultSet = statement.executeQuery();
            while (resultSet.next()) {
                System.out.println("ID: " + resultSet.getInt("id"));
                System.out.println("Username: " + resultSet.getString("username"));
                System.out.println("Email: " + resultSet.getString("email"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 5. 在 finally 块中确保资源被关闭
            // 注意:这里关闭的是连接,连接池会自动回收它,而不是真正关闭物理连接
            closeResources(resultSet, statement, connection);
        }
    }
    private void closeResources(ResultSet rs, PreparedStatement stmt, Connection conn) {
        try {
            if (rs != null) rs.close();
            if (stmt != null) stmt.close();
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

主流第三方连接池与 Tomcat 的集成

虽然 Tomcat 自带的连接池已经非常优秀,但很多开发者仍然偏爱功能更强大的第三方连接池,如 DruidHikariCP

HikariCP (目前性能之王)

HikariCP 以其无与伦比的性能和稳定性而闻名,被誉为“目前世界上最快的连接池”。

配置方式:

  1. 添加依赖:在你的 pom.xml 中添加 HikariCP 和数据库驱动的依赖。
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>5.0.1</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
  2. 配置 context.xml:配置方式与 Tomcat JDBC Pool 类似,只是 factory 类不同。
    <Resource name="jdbc/MyAppDB_Hikari"
              auth="Container"
              type="javax.sql.DataSource"
              factory="com.zaxxer.hikari.HikariJNDIFactory"
              driverClassName="com.mysql.cj.jdbc.Driver"
              jdbcUrl="jdbc:mysql://localhost:3306/your_database_name?useSSL=false&amp;serverTimezone=UTC"
              username="your_username"
              password="your_password"
              maximumPoolSize="100"
              minimumIdle="10"
              connectionTimeout="30000"
              idleTimeout="600000"
              maxLifetime="1800000"
    />

    Java 代码的查找和使用方式完全不变。

Druid (阿里开源,功能强大)

Druid 是阿里巴巴开源的数据库连接池,除了高性能外,它还提供了非常强大的监控功能,可以实时监控 SQL 执行情况、连接池状态等,在生产环境中非常有用。

配置方式:

  1. 添加依赖:在 pom.xml 中添加 Druid 和数据库驱动的依赖。
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.18</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
  2. 配置 context.xml:同样,通过 factory 类来指定 Druid。
    <Resource name="jdbc/MyAppDB_Druid"
              auth="Container"
              type="javax.sql.DataSource"
              factory="com.alibaba.druid.pool.DruidDataSourceFactory"
              driverClassName="com.mysql.cj.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/your_database_name?useSSL=false&amp;serverTimezone=UTC"
              username="your_username"
              password="your_password"
              maxActive="100"
              minIdle="10"
              initialSize="10"
              maxWait="60000"
      />

    Java 代码的查找和使用方式也完全不变。


最佳实践和注意事项

  1. 不要在代码中硬编码连接信息:始终使用 JNDI 配置,实现配置与代码的分离。
  2. 总是关闭资源:在 finally 块中关闭 Connection, Statement, ResultSet,防止资源泄漏,虽然连接池会回收连接,但 StatementResultSet 仍然需要手动关闭。
  3. 使用连接池的监控
    • Tomcat JDBC Pool: 可以通过 JMX 查看连接池状态。
    • Druid: 提供了一个内置的监控页面,你可以在 context.xml 中配置一个 Servlet 来访问它。
      <servlet>
          <servlet-name>DruidStatView</servlet-name>
          <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
          <init-param>
              <param-name>loginUsername</param-name>
              <param-value>admin</param-value>
          </init-param>
          <init-param>
              <param-name>loginPassword</param-name>
              <param-value>123456</param-value>
          </init-param>
      </servlet>
      <servlet-mapping>
          <servlet-name>DruidStatView</servlet-name>
          <url-pattern>/druid/*</url-pattern>
      </servlet-mapping>
  4. 合理设置连接池参数maxTotal, maxIdle 等参数需要根据你的应用负载和数据库承受能力来设置,初始值可以从 10030 开始,然后通过监控进行调优。
  5. 处理异常dataSource.getConnection() 可能会抛出 SQLException,确保你的代码能正确处理这种异常,比如记录日志并返回一个友好的错误页面给用户。
  6. 使用连接池工具类:为了避免每个 DAO 都写一遍 JNDI 查找的代码,可以创建一个工具类来获取 DataSource
特性 Tomcat JDBC Pool HikariCP Druid
来源 Tomcat 官方 独立开源社区 阿里巴巴
性能 非常好 顶级 非常好
功能 核心功能齐全 核心功能,极简 功能最全,带监控、防火墙
集成 与 Tomcat 无缝集成,配置最简单 需指定 factory,集成简单 需指定 factory,集成简单
适用场景 日常开发,追求简单稳定 对性能有极致要求的生产环境 对监控和功能有要求的生产环境

对于初学者和大多数项目,直接使用 Tomcat JDBC Pool 是最简单、最稳妥的选择,如果你追求极致性能,选择 HikariCP,如果你需要强大的监控和诊断功能,Druid 是不二之选。

分享:
扫描分享到社交APP
上一篇
下一篇