不能只靠try-catch后手动close,因为rs.close()抛异常会中断后续stmt.close()执行,或stmt为null时直接调用导致NullPointerException;推荐Java 7+使用try-with-resources,它自动逆序关闭、null安全、异常合并上报,且Connection、Statement、ResultSet均实现AutoCloseable。

为什么不能只靠try-catch后手动close?
很多人写完ResultSet和Statement就随手在finally块里调用close(),但实际中常遇到NullPointerException或SQLException: Closed Statement——因为rs.close()可能已抛异常,导致stmt.close()根本没执行;或者stmt本身初始化失败,stmt为null,直接调close()就NPE。
用try-with-resources是唯一推荐方式
Java 7+ 的try-with-resources能自动按声明逆序关闭资源,且对null安全(JDBC 4.1+驱动保证close()幂等),还能把多个资源的异常合并上报,比手写finally可靠得多。
关键点:
-
Connection、Statement、ResultSet都实现了AutoCloseable,可直接放进try括号里 - 声明顺序决定关闭顺序:先
ResultSet,再Statement,最后Connection(符合依赖关系) - 即使
executeQuery()抛异常,Statement仍会被关闭;即使ResultSet构造失败,Statement也不会漏关
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ResultSet rs = stmt.executeQuery()) {
stmt.setLong(1, userId);
while (rs.next()) {
// 处理数据
}
} catch (SQLException e) {
// 只需处理业务逻辑异常,资源关闭已由JVM保证
}
如果必须用旧式try-finally,怎么写才不出错?
仅限Java 6或无法升级驱动的场景。核心原则:每个资源单独try-catch,且判空后再close(),不依赖上层是否成功。
常见错误写法:if (rs != null) rs.close();看似安全,但close()本身可能抛SQLException,必须捕获。
- 每个
close()都包一层try-catch,且不向上抛 -
ResultSet在Statement前关闭(否则Oracle驱动可能报“invalid operation on closed statement”) -
Connection放在最外层finally,避免被内层异常吞掉
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement("SELECT * FROM users");
rs = stmt.executeQuery();
// ... 处理结果
} catch (SQLException e) {
// 处理异常
} finally {
if (rs != null) {
try { rs.close(); } catch (SQLException ignored) {}
}
if (stmt != null) {
try { stmt.close(); } catch (SQLException ignored) {}
}
if (conn != null) {
try { conn.close(); } catch (SQLException ignored) {}
}
}
Oracle特定坑:setFetchSize和close的冲突
Oracle JDBC驱动有个隐藏行为:调用ResultSet.close()时,如果底层游标还没取完数据(比如设置了大fetchSize但只遍历了前几行),驱动会主动发cancel请求中断服务器端查询。这本身没问题,但若此时网络抖动或数据库负载高,close()可能超时阻塞数秒——尤其在连接池紧张时,会拖垮整个线程。
解决方案很直接:
- 避免无意义的大
setFetchSize(如设成10000但通常只读几十行) - 业务上明确知道只读部分数据时,用
setMaxRows(n)替代靠break退出循环 - 若必须用大
fetchSize,确保ResultSet被完整遍历或显式调用rs.getStatement().cancel()再close()(但cancel()也有超时风险,慎用)
这个细节多数人忽略,直到压测时发现大量线程卡在oracle.jdbc.driver.T4CResultSet.close()里。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











