如何将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;
}
它似乎工作得很好,但我现在关心的是如何正确地分隔我的代码。我有以下三个相互关联的问题:
- 这是否相当有效,或者是否强烈建议只为工作人员在后台保持连接活动数小时,就像原始代码中所做的那样?
- 理想情况下,我希望将所有与SQL和JDBC相关的代码移到我的
Sql
类中,以便编写更清晰的分离代码。但是看起来Worker的publish
方法必须嵌套在结果集的try
-with-resource块中,因为如果该集不在块中,它就已经关闭了。如何将这两个任务分离到单独的类/方法中,而不会使连接永远处于活动状态、不会丢失连接跟踪、不会混淆SQL和图形用户界面/表模型代码? - 是否有办法在返回的
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
类实现AutoCloseable
和close
方法。因此,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
方法。
相关文章