如何将JDBC Swing Worker与连接池一起使用(理想情况下,同时将SQL和应用程序逻辑分开)?

我有一个带有Swing图形用户界面的Java应用程序,它使用Swing工作器从数据库(例如,SQLite或MySQL)中提取数据来填充JTable。Swing工作器使用JDBC,一次将多个行块放入表中。

为此,我使code found here适应了我的目的。该代码包含一个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类中,以便编写更清晰的分离代码。但是看起来Worker的publish方法必须嵌套在结果集的try-with-resource块中,因为如果该集不在块中,它就已经关闭了。如何将这两个任务分离到单独的类/方法中,而不会使连接永远处于活动状态、不会丢失连接跟踪、不会混淆SQL和图形用户界面/表模型代码?
  3. 是否有办法在返回的ResultSet对象被销毁或已到达while循环结束时自动关闭我在Sql类中创建的连接?

解决方案

在看到trashgod的comment后,我提出了一个解决方案,尽可能地将这两个问题分开,并在随后关闭连接。尽管知道它并不完美,但我仍将其作为答案发布,希望有人能提出更好的方案。

在我的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辅助进程可以在try-with-resource块中使用它,并且在完成后它将自动关闭连接、语句和结果集。Swing工作器按如下方式处理结果:

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;
}
由于AutoCloseable接口,doInBackground函数在处理结果集后关闭连接、语句和结果集。与我之前的版本相比,性能似乎没有明显的差异,而且分离到Sql类和图形用户界面/模型关注点更严格,这使得每个主题的专家都更易于维护。

但是,这仍然有一些缺点:代码要长得多。如果其他人开始使用getResultSet方法,他们可能没有意识到连接仍然需要关闭,可能无法使用带资源的try或手动调用close。而且仍然没有完美的关注点分离,因为Swing工作者仍然必须关闭连接并处理结果集(即JDBC代码)。但我看不出如何才能实现更好的分离(欢迎发表评论)。至少在这段代码中,我将所有的SQL和一些JDBC内容放在Sql类中,即使有一些开销。

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

相关文章