使用ExecutorService.submit执行MySQL查询时遭遇ResultSet已关闭异常
异常原因、代码问题及解决方案
异常原因
java.sql.SQLException: Operation not allowed after ResultSet closed 异常的核心原因是:你传递到异步线程的ResultSet在异步任务执行前就已经被关闭了。
代码中的问题
资源生命周期错误
在executeUpdate方法中,Connection、PreparedStatement和ResultSet都是通过try-with-resources声明的,这些资源会在try代码块执行完毕后自动关闭。而你返回的lambda() -> resultSet要等到异步线程执行call()方法时才会获取ResultSet,此时原方法的try块已经结束,所有相关JDBC资源都已被释放,异步线程自然无法使用已关闭的ResultSet。误解问题本质
你尝试用synchronized和volatile修饰来解决问题,但这个异常和线程同步无关,完全是JDBC资源的生命周期管理错误,所以这些操作毫无作用。违反JDBC资源依赖规则
JDBC的ResultSet依赖于创建它的PreparedStatement,而PreparedStatement又依赖于Connection。一旦PreparedStatement或Connection被关闭,对应的ResultSet也会被强制关闭,跨线程传递ResultSet本身就是错误的做法。
解决方案
最合理的做法是在JDBC资源有效时提取所需数据,再将数据传递到异步线程,而不是传递底层的JDBC资源。修改代码如下:
1. 修改executeUpdate方法,返回封装好结果的Callable<Integer>
public Callable<Integer> executeUpdate(String query, Column<?>... columns) { return () -> { try (Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) { for (int i = 0; i < columns.length; i++) { columns[i].setValue(statement, i + 1); } statement.executeUpdate(); try (ResultSet resultSet = statement.getGeneratedKeys()) { return resultSet.next() ? resultSet.getInt(1) : -1; } } catch (SQLException e) { throw new RuntimeException(e); } }; }
2. 修改调用逻辑,直接获取异步返回的主键值
Callable<Integer> callable = executeUpdate(REGISTER_NEW_ACCOUNT, new VarcharColumn("name", name), new VarcharColumn("uuid", uuid.toString()), new VarcharColumn("rank", PlayerRank.NONE.name()), new VarcharColumn("packageRank", PlayerRank.NONE.name()) ); Future<Integer> future = SQL_EXECUTOR.submit(callable); try { return future.get(); } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); }
关键注意事项
- JDBC资源(
Connection、Statement、ResultSet)线程不安全,禁止跨线程传递或共享。 - try-with-resources会自动关闭所有实现
AutoCloseable接口的资源,务必确保资源的使用范围在try块内部,不要返回已被关闭的资源。 - 连接池的
Connection调用close()时是归还到池中,而非真正销毁,但依赖它的Statement和ResultSet仍会被关闭,同样不能在Connection归还后使用这些资源。 - 异步执行数据库操作时,应将完整的JDBC操作逻辑(资源创建、使用、关闭)封装到异步任务中,确保所有操作在同一个线程内完成。
内容的提问来源于stack exchange,提问作者Santiago Narvaez
相关产品推荐
相关产品推荐

