- 什么是连接池?为什么需要它?
- Tomcat 内置的连接池:Tomcat JDBC Pool
- 如何配置和使用 Tomcat JDBC Pool
- 主流第三方连接池(Druid, HikariCP)与 Tomcat 的集成
- 最佳实践和注意事项
什么是连接池?为什么需要它?
问题背景
在传统的数据库操作中,每次需要与数据库交互时,应用程序都需要执行以下步骤:

- 加载驱动 (虽然现代 JDBC 驱动会自动处理)
- 建立连接 (
DriverManager.getConnection()) - 执行 SQL
- 关闭连接 (
connection.close())
这个过程非常耗时,尤其是“建立连接”这一步,涉及到网络通信和数据库服务端的认证、资源分配等,对于一个高并发的 Web 应用(如 Tomcat 托管的网站),如果每个请求都创建和销毁一个连接,性能将会急剧下降,数据库也可能因为连接数耗尽而崩溃。
解决方案:连接池
数据库连接池是一个预先创建和管理好多个数据库连接的容器,当应用程序需要一个连接时,它不是去创建一个新的,而是从连接池中“借用”一个,使用完毕后,也不是真的关闭,而是“归还”给连接池,以供下一个请求使用。
连接池带来的好处:
- 性能提升:复用连接,避免了频繁创建和销毁连接的开销。
- 资源控制:限制了应用对数据库的最大连接数,防止因连接数过多导致数据库崩溃。
- 管理方便:可以集中管理连接,监控连接状态,实现连接的自动回收等。
Tomcat 内置的连接池:Tomcat JDBC Pool
从 Tomcat 7.0 开始,官方推荐使用 Tomcat JDBC Pool,它取代了之前基于 Apache Commons DBCP 的旧连接池。

Tomcat JDBC Pool 的优势
- 高性能:经过了高度优化,性能非常出色,在某些场景下甚至优于一些第三方连接池。
- 异步支持:支持异步获取连接,可以充分利用 NIO 提高并发性能。
- 高可用性:内置连接泄漏检测、连接验证等功能。
- 与 Tomcat 无缝集成:作为 Tomcat 的一部分,配置和使用非常方便。
- 兼容 JDBC 4.0+:支持标准的 JDBC API。
如何配置和使用 Tomcat JDBC Pool
配置 Tomcat 连接池主要有两种方式:
- 在
context.xml中配置(推荐):配置与 Web 应用解耦,一个连接池可以被该应用中的所有 Servlet/JSP/DAO 共享,并且可以随 Web 应用一起部署和移除。 - 在
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 文件。

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&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 1或SELECT 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 自带的连接池已经非常优秀,但很多开发者仍然偏爱功能更强大的第三方连接池,如 Druid 和 HikariCP。
HikariCP (目前性能之王)
HikariCP 以其无与伦比的性能和稳定性而闻名,被誉为“目前世界上最快的连接池”。
配置方式:
- 添加依赖:在你的
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> - 配置
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&serverTimezone=UTC" username="your_username" password="your_password" maximumPoolSize="100" minimumIdle="10" connectionTimeout="30000" idleTimeout="600000" maxLifetime="1800000" />Java 代码的查找和使用方式完全不变。
Druid (阿里开源,功能强大)
Druid 是阿里巴巴开源的数据库连接池,除了高性能外,它还提供了非常强大的监控功能,可以实时监控 SQL 执行情况、连接池状态等,在生产环境中非常有用。
配置方式:
- 添加依赖:在
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> - 配置
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&serverTimezone=UTC" username="your_username" password="your_password" maxActive="100" minIdle="10" initialSize="10" maxWait="60000" />Java 代码的查找和使用方式也完全不变。
最佳实践和注意事项
- 不要在代码中硬编码连接信息:始终使用 JNDI 配置,实现配置与代码的分离。
- 总是关闭资源:在
finally块中关闭Connection,Statement,ResultSet,防止资源泄漏,虽然连接池会回收连接,但Statement和ResultSet仍然需要手动关闭。 - 使用连接池的监控:
- 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>
- 合理设置连接池参数:
maxTotal,maxIdle等参数需要根据你的应用负载和数据库承受能力来设置,初始值可以从100和30开始,然后通过监控进行调优。 - 处理异常:
dataSource.getConnection()可能会抛出SQLException,确保你的代码能正确处理这种异常,比如记录日志并返回一个友好的错误页面给用户。 - 使用连接池工具类:为了避免每个 DAO 都写一遍 JNDI 查找的代码,可以创建一个工具类来获取
DataSource。
| 特性 | Tomcat JDBC Pool | HikariCP | Druid |
|---|---|---|---|
| 来源 | Tomcat 官方 | 独立开源社区 | 阿里巴巴 |
| 性能 | 非常好 | 顶级 | 非常好 |
| 功能 | 核心功能齐全 | 核心功能,极简 | 功能最全,带监控、防火墙 |
| 集成 | 与 Tomcat 无缝集成,配置最简单 | 需指定 factory,集成简单 |
需指定 factory,集成简单 |
| 适用场景 | 日常开发,追求简单稳定 | 对性能有极致要求的生产环境 | 对监控和功能有要求的生产环境 |
对于初学者和大多数项目,直接使用 Tomcat JDBC Pool 是最简单、最稳妥的选择,如果你追求极致性能,选择 HikariCP,如果你需要强大的监控和诊断功能,Druid 是不二之选。
