关闭PreparedStatement会关闭关联ResultSet吗?求替代实现方案
问题1:关闭PreparedStatement时,是否会同时关闭与其关联的ResultSet?
按照JDBC的官方规范,当你关闭PreparedStatement的时候,和它绑定的ResultSet是会被自动关闭的。不过这里要提一句,虽然规范是这么定的,但有些非常老旧的JDBC驱动可能存在不遵循的情况,不过现在主流的数据库驱动(比如MySQL Connector/J、Oracle JDBC Driver)都是严格遵守这个规则的,所以一般不用太担心例外情况。
问题2:如何避免关闭PreparedStatement时关闭需要返回的ResultSet?
你的代码问题很典型:你在函数里先关闭了PreparedStatement,但ResultSet是依赖这个Statement存在的,所以关闭Statement后,ResultSet就失效了,自然没法正常返回使用。这里给你两种可行的解决方案,推荐优先用第二种:
方案1:让调用方负责管理所有数据库资源
不要在函数内部关闭PreparedStatement,而是把ResultSet和PreparedStatement一起返回给调用方,让调用方在使用完ResultSet后,手动关闭这两个资源。为了方便管理,你可以自定义一个简单的容器类来封装这两个对象,最好让它实现AutoCloseable接口,这样就能用Java的try-with-resources语法自动关闭资源,避免泄漏。
示例代码:
// 自定义资源容器类,实现AutoCloseable以便自动关闭 class QueryResource implements AutoCloseable { private ResultSet rs; private PreparedStatement ps; // 构造器和getter方法 public QueryResource(ResultSet rs, PreparedStatement ps) { this.rs = rs; this.ps = ps; } public ResultSet getRs() { return rs; } public PreparedStatement getPs() { return ps; } // 实现AutoCloseable的close方法,按顺序关闭资源 @Override public void close() throws SQLException { if (rs != null) rs.close(); if (ps != null) ps.close(); } } // 你的查询函数 QueryResource fnName(Connection conn) { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement("<你的查询语句>"); rs = ps.executeQuery(); return new QueryResource(rs, ps); } catch (SQLException e) { // 发生异常时,先关闭已创建的资源 try { if (rs != null) rs.close(); if (ps != null) ps.close(); } catch (SQLException ex) { ex.printStackTrace(); } throw e; // 或者根据业务需求处理异常 } } // 调用方的使用方式,用try-with-resources自动关闭 try (QueryResource resource = fnName(yourConnection)) { ResultSet rs = resource.getRs(); if (rs != null) { while (rs.next()) { // 处理ResultSet中的数据 } } } catch (SQLException e) { // 处理异常 }
方案2:将ResultSet数据读取到内存集合中返回(推荐)
这是更稳妥也更符合Java资源管理规范的做法:不要直接返回依赖数据库连接的ResultSet,而是把ResultSet里的数据读取到内存中的集合(比如List)或者实体对象里,然后关闭所有数据库资源,最后返回这个内存集合。这样做的好处是,数据脱离了数据库连接,不会占用连接资源,调用方也不用关心数据库资源的关闭问题,而且数据可以在多线程环境使用、序列化等。
示例代码:
// 假设你有一个User实体类对应查询结果 class User { private int id; private String name; // 构造器、getter、setter方法 } List<User> fnName(Connection conn) { List<User> userList = new ArrayList<>(); // 用try-with-resources自动关闭PreparedStatement和ResultSet try (PreparedStatement ps = conn.prepareStatement("<你的查询语句>"); ResultSet rs = ps.executeQuery()) { while (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setName(rs.getString("name")); // 给其他字段赋值 userList.add(user); } } catch (SQLException e) { e.printStackTrace(); // 根据业务需求处理异常,比如返回空列表或者抛出异常 } return userList; } // 调用方直接使用返回的List即可,不用管资源关闭 List<User> users = fnName(yourConnection); for (User user : users) { // 处理数据 }
这种方式之所以更推荐,是因为ResultSet是“懒加载”的,它的存在依赖于数据库连接和Statement,长时间持有会占用连接池资源,甚至导致连接泄漏;而内存集合则完全脱离了数据库资源,使用起来更安全灵活。
内容的提问来源于stack exchange,提问作者sri ram

