并行执行SQL查询关闭Statement与ResultSet引发竞争条件如何解决
问题根本原因
你遇到的ResultSet closed异常和并行/串行执行没有本质关系,核心问题出在ConnectionManager类的设计错误:
- 类内维护了全局静态的Connection实例,每次调用
getConnection()都会覆盖这个静态变量,同时closeConnection()关闭的也是这个全局共享的连接实例 - 当两个查询同时(甚至一前一后串行)执行时,只要有一个查询执行到finally块调用了
closeConnection(),另一个查询还在使用的连接、Statement、ResultSet都会被连带关闭,自然抛出异常
你提到的串行执行方案大概率能临时跑通,但属于掩盖设计缺陷,只要后续有其他业务逻辑调用getConnection(),还是会随时触发相同问题,完全不推荐作为最终解决方案。
最优解决方案
1. 重构ConnectionManager,移除全局共享连接
删掉静态全局连接变量,每次请求返回独立连接,连接关闭逻辑改为只关闭传入的指定连接:
public class ConnectionManager { public static Connection getConnection() throws SQLException { // 每次调用返回独立连接,不全局共享 return DriverManager.getConnection(Constants.CONNECTION_URL); } public static void closeConnection(Connection connection){ if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void closeResultSetAndStatement(ResultSet resultSet, Statement statement){ if (resultSet != null) { try { resultSet.close(); } catch (SQLException sqlEx) { } } if (statement != null) { try { statement.close(); } catch (SQLException sqlEx) { } } } }
2. 修改查询方法,每个方法持有独立连接
两个查询方法分别维护自己的连接,关闭时只关闭当前方法使用的连接,不会互相影响,以queryAppointments为例,queryCustomers按相同逻辑修改即可:
public ObservableList<Appointment> queryAppointments() { ObservableList<Appointment> listAppointments = FXCollections.observableArrayList(); Connection conn = null; // 方法内部持有独立连接,不共享 Statement statement = null; ResultSet resultSet = null; try { conn = ConnectionManager.getConnection(); statement = conn.createStatement(); resultSet = statement.executeQuery(countriesQuery); while (resultSet.next()) { int appointmentId = resultSet.getInt("Appointment_ID"); String title = resultSet.getString("Title"); String description = resultSet.getString("Description"); // 此处省略创建appointment对象的相关代码 listAppointments.add(appointment); } return listAppointments; } catch (SQLException e) { e.printStackTrace(); return null; } finally { // 按顺序关闭资源,只关当前方法持有的连接,不影响其他查询 ConnectionManager.closeResultSetAndStatement(resultSet, statement); ConnectionManager.closeConnection(conn); } }
3. 保留并行查询逻辑,性能更高
修改完成后你原来的并行查询逻辑完全可以正常运行,不需要改成串行,可通过allOf等待两个查询全部完成后再处理结果,执行效率远高于串行:
public static void main(String[] args) { CompletableFuture<ObservableList<Appointment>> appointmentsFuture = mainRepository.getAppointments(); CompletableFuture<ObservableList<Customer>> customersFuture = mainRepository.getCustomers(); // 两个查询并行执行,全部完成后再处理结果 CompletableFuture.allOf(appointmentsFuture, customersFuture) .thenRun(() -> { try { ObservableList<Appointment> appointments = appointmentsFuture.get(); ObservableList<Customer> customers = customersFuture.get(); // 自定义业务逻辑处理两个结果集 } catch (Exception e) { e.printStackTrace(); } }); }
进阶优化建议
如果后续查询量变大,每次新建连接的开销过高,可以引入HikariCP等数据库连接池替换当前直接调用DriverManager.getConnection的逻辑,连接池会自动管理连接复用,性能更高,上层使用逻辑不需要做任何修改。
内容的提问来源于stack exchange,提问作者WHOATEMYNOODLES
相关产品推荐
相关产品推荐

