首页 > 解决方案 > 如何将 JDBC Swing Worker 与连接池一起使用(最好是在分离 SQL 和应用程序逻辑时)?

问题描述

我有一个带有 Swing GUI 的 Java 应用程序,它使用 Swing 工作者从数据库(例如 SQLite 或 MySQL)中提取数据以填充 JTable。swing worker 使用 JDBC 并一次将大块的行放入表中。

为此,我根据我的目的调整了此处找到的代码。该代码包含一个JDBCModel类,该类扩展了一个AbstractTableModel以存储 JTable 的数据。该代码还包含一个JDBCWorker扩展类,SwingWorker用于访问数据库并将行添加到表模型中。

的构造函数JDBCModel首先建立一个连接,执行一个查询,然后创建一个ResultSet

try {
    Statement s = conn.createStatement();
    rs = s.executeQuery(query);
    meta = rs.getMetaData();
    JDBCWorker worker = new JDBCWorker();
    jpb.setIndeterminate(true);
    worker.execute();
} catch (SQLException e) {
    e.printStackTrace(System.err);
}

然后JDBCWorker简单地遍历结果集并为表创建行。被JDBCWorker定义为 中的私有类JDBCModel。这是JDBCWorker遍历结果集的方式:

protected List<Row> doInBackground() {
    try {
        while (rs.next()) {
            Row r = new Row();
            // omitting some additional computations for brevity...
            publish(r);
        }
    } catch (SQLException e) {
        e.printStackTrace(System.err);
    }
    return data;
}

在我自己的代码中,我使用连接池而不是保持同一个连接处于活动状态。我已将代码修改如下,以便能够从我在单独的Sql类中定义的数据源请求新连接。我还把私人JDBCWorker班级移出了JDBCModel班级。每次需要重新填充表时,都会创建一个新的工作人员。这就是连接池中的worker现在的样子;它使用try-with-resources 在使用后自动关闭连接、语句和结果集:

protected List<Row> doInBackground() {
    try (Connection conn = sql.getDataSource().getConnection();
            PreparedStatement statement = conn.prepareStatement(query)) {
        ResultSet rs = statement.executeQuery();
        while (rs.next()) {
            Row r = new Row();
            // omitting some additional computations for brevity...
            publish(r);
        }
    } catch (SQLException e) {
        e.printStackTrace(System.err);
    }
    return null;
}

它似乎工作正常,但我现在担心正确分离我的代码。我有以下三个相互关联的问题:

  1. 这是否相当有效,或者是否强烈建议像在原始代码中所做的那样,只为工作人员在后台保持连接数小时?
  2. 理想情况下,我想将所有与 SQL 和 JDBC 相关的代码移到我的Sql班级中,以便编写更清晰分离的代码。但看起来publish工作人员的方法必须嵌套在try结果集的 -with-resources 块内,因为如果它不在块内,该集将已经关闭。如何将这两个任务分成单独的类/方法,而不会使连接永远保持活动状态、丢失连接跟踪或混合 SQL 和 GUI/表模型代码?
  3. 有没有办法在返回的对象被销毁或到达循环末尾时自动关闭我在Sql类中创建的连接?ResultSetwhile

标签: javajdbcconnection-poolingswingworkerseparation-of-concerns

解决方案


看到垃圾神评论后,我想出了一个解决方案,尽可能将这两个问题分开,并根据有用的评论,稍后关闭连接。尽管知道它并不完美,但我仍将其发布为答案,希望有人能想出更好的东西。

在我的Sql课程中,我创建了一个方法来执行查询并将结果集与语句和连接一起保存并返回所有内容:

public SqlStuff getSqlStuff() {
    String query = "SELECT * FROM MyTable;";
    ResultSet rs = null;
    Connection conn = null;
    PreparedStatement statement = null;
    try {
        conn = getDataSource().getConnection();
        statement = conn.prepareStatement(query);
        rs = statement.executeQuery();
    } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } finally {
        // nothing to close here because the result set is still needed
    }
    SqlStuff s = new SqlStuff(rs, tableStatement, conn);
    return s;
}

一旦数据被提取,连接、语句和结果集需要由 Swing Worker 关闭,因此它们还不能关闭,并且都需要存储在一个SqlStuff对象中并为此目的返回。这个容器对象定义如下:

public class SqlStuff implements AutoCloseable {
    ResultSet rs;
    PreparedStatement ps;
    Connection c;
    
    public SqlStuff(ResultSet rs, PreparedStatement ps, Connection c) {
        this.rs = rs;
        this.ps = ps;
        this.c = c;
    }
    
    public void close() {
        rs.close();
        ps.close();
        c.close();
    }
    
    public ResultSet getResultSet() {
        return rs;
    }
}

SqlStuff实现AutoCloseableclose方法。因此,swing worker 可以在try-with-resources 块中使用它,完成后它会自动关闭连接、语句和结果集。swing worker 处理结果如下:

protected List<TableDocument> doInBackground() {
    
    try (SqlStuff s = sql.getSqlStuff();
            ResultSet rs = s.getResultSet()) {
        while (rs.next()) {
            Row r = new Row(rs.getString(1));
            // omitting additional getString and getInt calls for brevity
            publish(r);
        }
    } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}

由于接口,该doInBackground函数在结果集处理完毕后关闭连接、语句和结果集AutoCloseable。与我之前的版本相比,似乎没有明显的性能差异,并且对Sql类和 GUI/模型关注点的分离更加严格,这使得每个主题的专家都更易于维护。

但是,这仍然有一些缺点:代码要长得多。如果其他人开始使用该getResultSet方法,他们可能没有意识到连接仍然需要关闭,并且可能无法使用try-with-resources 或close手动调用。并且仍然没有完美的关注点分离,因为 Swing Worker 仍然必须关闭连接并通过结果集工作,这是 JDBC 代码。但我看不到如何实现更好的分离(欢迎评论)。至少有了这段代码,我在Sql课堂上拥有了所有的 SQL 和一些 JDBC 的东西,即使有一些开销。

我想作为替代方案,我可以将整个doInBackground函数移到单独的Sql类中,并在 swing worker 中引用它来完成工作。但我不知道如何告诉函数然后publish从哪里获取方法。


推荐阅读