在Java应用程序中,数据库连接是一种宝贵的稀缺资源,正确地管理并关闭这些连接,对于保证应用程序的稳定性、性能和可伸缩性至关重要,未能妥善关闭连接会导致资源泄漏,最终耗尽连接池,使应用程序因无法获取新连接而崩溃,掌握如何正确关闭数据库连接是每个Java开发者的基本功。

传统方式:try-catch-finally
在JDBC 4.0之前,最经典和保险的做法是使用try-catch-finally块,核心思想是将关闭资源的代码放在finally块中,因为无论try块中的代码是否抛出异常,finally块中的代码都保证会被执行。
一个完整的关闭流程不仅包括Connection对象,还应包括由它创建的Statement(或PreparedStatement)和ResultSet对象,关闭的顺序应与创建的顺序相反,即先关闭ResultSet,再关闭Statement,最后关闭Connection。
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 1. 获取连接
connection = dataSource.getConnection();
// 2. 创建PreparedStatement
statement = connection.prepareStatement("SELECT * FROM users WHERE id = ?");
statement.setInt(1, 123);
// 3. 执行查询
resultSet = statement.executeQuery();
// 4. 处理结果集
while (resultSet.next()) {
// ... 处理数据
}
} catch (SQLException e) {
// 处理异常
e.printStackTrace();
} finally {
// 5. 在finally块中确保资源被关闭
// 关闭顺序:ResultSet -> Statement -> Connection
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} 这种方式虽然安全,但代码显得非常冗长和繁琐,大量的try-catch嵌套降低了代码的可读性。
现代方式:try-with-resources
从Java 7开始,引入了try-with-resources语句,极大地简化了资源管理,任何实现了java.lang.AutoCloseable接口(Closeable接口的父类)的类都可以在try语句的括号中声明,当代码块执行完毕后,无论正常结束还是因为异常,JVM都会自动调用这些资源的close()方法,关闭顺序同样是声明顺序的反向。

这被认为是目前关闭JDBC资源的最佳实践。
// 使用 try-with-resources 自动关闭资源
try (Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE id = ?")) {
statement.setInt(1, 123);
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
// ... 处理数据
}
} // ResultSet 在这里自动关闭
} catch (SQLException e) {
// 处理异常
e.printStackTrace();
} // Statement 和 Connection 在这里自动关闭 如上所示,代码变得异常简洁、清晰,并且消除了忘记关闭资源的风险,编译器会自动生成类似于finally块中关闭资源的代码,确保了安全性。
连接池环境下的“关闭”
在现代企业级应用中,我们通常使用数据库连接池(如HikariCP、Druid、C3P0等)来管理连接,在这种环境下,调用connection.close()的含义与物理关闭连接有所不同。
当从连接池中借用一个连接时,connection.close()并不会真正地关闭与数据库的TCP套接字连接,相反,它会将这个连接“归还”给连接池,将其状态重置为可用,以便其他线程可以再次借用,这样做避免了频繁创建和销毁物理连接所带来的巨大开销。

即使在连接池环境下,遵循try-with-resources的模式来“关闭”连接也是完全正确的,这正是连接池设计者所期望的使用方式。
| 方法 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
try-catch-finally | 兼容性好(Java 7之前),逻辑明确 | 代码冗长,可读性差,容易出错 | ★☆☆☆☆ |
try-with-resources | 代码简洁,安全可靠,可读性高 | 需要Java 7及以上版本支持 | ★★★★★ |
相关问答FAQs
问题1:如果忘记关闭数据库连接会发生什么?
答: 如果忘记关闭数据库连接,这些连接会一直保持打开状态,占用系统资源,在使用连接池的场景下,这会导致连接池中的可用连接越来越少,最终被耗尽,当应用程序请求新的数据库连接时,会因为无法从池中获取可用连接而长时间阻塞或直接抛出异常,导致整个应用服务瘫痪,这种现象被称为“连接泄漏”,是常见的严重性能问题之一。
答: 理论上,关闭一个Connection对象会使其对应的所有Statement和ResultSet对象变为无效,并且大多数JDBC驱动在关闭Connection时也会尝试关闭与之相关的资源,依赖这种行为是不安全的,因为JDBC规范并未强制要求这一点,最佳实践是显式地、按正确顺序(先ResultSet,后Statement,最后Connection)关闭所有资源,使用try-with-resources可以完美地自动处理这一顺序,无需手动干预,是最为稳妥的方式。
【版权声明】:本站所有内容均来自网络,若无意侵犯到您的权利,请及时与我们联系将尽快删除相关内容!
发表回复