在Java中操作数据库时,Connection、PreparedStatement、ResultSet等资源都属于需要手动关闭的资源,这类资源如果不及时释放,会持续占用数据库连接池的连接,最终导致连接池耗尽,新的数据库请求无法获取连接。而finally块的特性是无论try块中的代码是否抛出异常,都会被执行,因此非常适合用来做资源释放的工作。

安全释放资源的核心原则
要在finally中安全释放数据库资源,需要遵循几个核心原则,避免释放过程中出现新的异常或者遗漏释放的情况:
- 按照资源创建的逆序释放,先释放ResultSet,再释放PreparedStatement,最后释放Connection,避免上层资源释放后下层资源无法正常关闭的问题。
- 每个资源的释放都需要单独做空值校验,防止资源没有被成功创建时调用close方法抛出空指针异常。
- 每个资源的close操作都需要单独放在try-catch块中,避免第一个资源释放抛出异常导致后续资源无法释放。
标准JDBC资源释放示例
以下是一个完整的数据库查询示例,展示了如何在finally块中安全释放所有相关资源:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcResourceReleaseDemo {
private static final String JDBC_URL = "jdbc:mysql://127.0.0.1:3306/test_db?useSSL=false";
private static final String JDBC_USER = "root";
private static final String JDBC_PASSWORD = "123456";
public void queryUserData() {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 1. 获取数据库连接
conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
// 2. 创建预编译语句
String sql = "SELECT id, username FROM user WHERE age > ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 18);
// 3. 执行查询获取结果集
rs = pstmt.executeQuery();
// 4. 处理结果集
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
System.out.println("用户ID:" + id + ",用户名:" + username);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放结果集,单独处理异常
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 释放预编译语句,单独处理异常
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 释放数据库连接,单独处理异常
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
Java 7之后的try-with-resources优化
Java 7引入了try-with-resources语法,能够自动关闭实现了AutoCloseable接口的资源,包括JDBC的Connection、PreparedStatement、ResultSet,这种方式可以简化资源释放的代码,本质上也是基于finally机制实现的,安全性更高。
使用try-with-resources的示例如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TryWithResourcesDemo {
private static final String JDBC_URL = "jdbc:mysql://127.0.0.1:3306/test_db?useSSL=false";
private static final String JDBC_USER = "root";
private static final String JDBC_PASSWORD = "123456";
public void queryUserData() {
String sql = "SELECT id, username FROM user WHERE age > ?";
try (
// 资源会在try块执行结束后自动关闭,顺序和声明顺序相反
Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(sql);
) {
pstmt.setInt(1, 18);
// ResultSet也实现了AutoCloseable,可以放在try-with-resources中自动关闭
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
System.out.println("用户ID:" + id + ",用户名:" + username);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
常见错误与避坑点
在实际开发中,很多开发者在finally中释放资源时会犯一些常见错误,需要特别注意:
- 错误1:不做空值校验直接调用close方法,当资源创建失败时变量为null,调用close会抛出
NullPointerException。 - 错误2:所有资源的close方法放在同一个try-catch块中,第一个资源close抛出异常后,后续资源的close不会执行,导致资源泄漏。
- 错误3:释放顺序错误,先释放Connection再释放PreparedStatement和ResultSet,此时上层连接已经关闭,下层资源close可能会抛出异常。
- 错误4:在finally块中做过多的业务逻辑,finally块应该只做资源释放相关的操作,避免引入新的异常影响资源释放流程。
不同场景的注意事项
如果使用数据库连接池比如HikariCP、Druid等,调用Connection的close方法并不是真正关闭物理连接,而是将连接归还到连接池中,但依然需要在finally中执行close操作,否则连接不会被归还,同样会导致连接池耗尽。
如果使用的是Spring框架的JdbcTemplate等封装好的数据库操作工具,底层已经帮我们处理了资源释放的逻辑,不需要手动在finally中释放资源,但了解底层的释放机制有助于排查资源相关的问题。
Javafinally数据库资源释放ConnectionPreparedStatement修改时间:2026-06-10 07:03:26