You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

并行执行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 06:18:05