分页
由于 CachedRowSet 是将数据临时存储在内存中,因此对于许多 SQL 查询,会返回大量的数据。如果将整个结果集全部存储在内存中会占用大量的内存,有时甚至是不可行的。对此 CachedRowSet 提供了分批从 ResultSet 中获取数据的方式,这就是分页。应用程序可以简单的通过 setPageSize 设置一页中数据的最大行数。也就是说,如果页大小设置为 5,一次只会从数据源获取 5 条数据。下面的代码示范了如何进行简单分页操作。(分页部分代码默认 ORDERS 表中有 10 条数据)
清单 11. 分页代码一
ResultSet rs = stmt.executeQuery(DBCreator.SQL_SELECT_ORDERS);
CachedRowSet cachedRS = new CachedRowSetImpl(); // 设置页大小
cachedRS.setPageSize(4);
cachedRS.populate(rs, 1);
while (cachedRS.nextPage()) { printRowSet(cachedRS); }
while (cachedRS.previousPage()) { printRowSet(cachedRS); }
可以看到只需要在 populate 之前使用 setPageSize 设置页的大小,就可以轻松实现分页了。每次调用 nextPage 或 previousPage 进行翻页后,行游标都会被自动移动到当前页第一行的前面,并且只能在当前页内移动。这样我们对每一页都可以像新的数据集一样进行遍历,非常方便。这里需要注意的是:
用来填充 CachedRowSet 的 ResultSet 必须是可滚动的(Scrollable)。
populate 必须使用有两个参数的版本,否则无法进行分页。读者可以将 cachedRS.populate(rs, 1); 换成 cachedRS.populate(rs); 看看会有怎样的情况发生。
ResultSet rs 必须在遍历完毕后才能关闭,否则翻页遍历时会抛 SQLException。
我们注意到在使用分页遍历数据集时,nextPage() 是最先被调用的,也就是说页与行相似,最开始的页游标是指向第 0 页的,通过 nextPage() 方法移到第一页,这样就可以用非常简洁的代码遍历数据集。那么如果在第 0 页时(第一次调用 nextPage)之前使用 next 遍历当前页会是怎样的结果呢?
清单 12. 分页代码二
ResultSet rs = stmt.executeQuery(DBCreator.SQL_SELECT_ORDERS);
CachedRowSet cachedRS = new CachedRowSetImpl(); // 设置页大小
cachedRS.setPageSize(4);
cachedRS.populate(rs, 1);
printRowSet(cachedRS);
while (cachedRS.nextPage()) {
printRowSet(cachedRS);
}
我们看到第一页被输出了两次。也就是说使用 next 始终是可以遍历第一页的,而每次 nextPage 后,行游标都会被重置到当前页的第一行数据之前。对于使用 execute 填充数据的方式,通过 setPageSize 同样可以实现分页。
setMaxRows 可以设置 CachedRowSet 可遍历的最大行数。下面是 setMaxRows 和 setPageSize 同时使用的例子。
清单 13. 分页代码三
ResultSet rs = stmt.executeQuery(DBCreator.SQL_SELECT_ORDERS);
CachedRowSet cachedRS = new CachedRowSetImpl();
cachedRS.setPageSize(4);
cachedRS.setMaxRows(7);
cachedRS.populate(rs, 1);
while (cachedRS.nextPage()) { printRowSet(cachedRS); }
rs.close();
上面的例子中,分别将页大小设置为 4,最大行数设置为 7(设置页大小时,如果最大行数不等于 0,就必须小于等于最大行数),然后遍历打印所有行,输出如下。
清单 14. 清单 13 中的代码执行结果 The data in RowSet: 1 1 Book 2 1 Compute 3 2 Phone 4 2 Java The data in RowSet: 5 2 Test 6 1 C++ 7 2 Perl
我们注意到,虽然满足 Select 条件的数据有 10 条,但由于我们使用 setMaxRows 设置了允许的最大行数,所以最终我们只得到了 7 条数据。另外 setPageSize 只会改变下一次填充页时的大小,无法影响当前页的大小。我们可以在数据填充完毕后再改变页的大小。
清单 15. 分页代码四
ResultSet rs = stmt.executeQuery(DBCreator.SQL_SELECT_ORDERS);
CachedRowSet cachedRS = new CachedRowSetImpl();
cachedRS.setPageSize(4);
cachedRS.populate(rs, 1);
cachedRS.setPageSize(3);
while (cachedRS.nextPage()) { printRowSet(cachedRS); }
rs.close();
我们在 populate 数据之前设置的页大小是 4,在 populate 数据之后又将页大小设置为 3,然后遍历输出结果。
清单 16. 清单 15 中的代码执行结果
The data in RowSet: 1 1 Book 2 1 Compute 3 2 Phone 4 2 Java The data in RowSet: 5 2 Test 6 1 C++ 7 2 Perl The data in RowSet: 8 1 Ruby 9 1 Erlang 10 2 Python
我们发现除了第一页有 4 条数据外,其余页都只有 3 条数据。实际上 populate 中就已经对第一页数据进行了填充,并且使用之前设置的 4 作为页大小。所以 populate 之后设置的页大小就只能从第二页开始起作用了。setMaxSize 与此相似,也是在每次填充页时计算。
应注意的问题
在 JDK 5.0 中,当删除一行中某列值为 null 时,会抛出 NullPointerException。例如,表 CUSTOMERS 中第二行第三列的值为 null。假设 cachedRS 里面填充着表 CUSTOMERS 的数据,那么下段代码在 JDK 5.0 下运行时会抛出 NullPointerException。在 JDK 6.0 中,此问题已得到修正。
清单 17.
cachedRS.absolute(2);
cachedRS.deleteRow();
cachedRS.acceptChanges();
使用 WebRowSet
WebRowSet 继承于 CachedRowSet,因此用来填充 CachedRowSet 的方式同样适用于 WebRowSet。WebRowSet 也可以读取一个符合规范的 XML 文件,填充自己。假定 CUSTOMERS.xml 是一个符合规范的 XML 文件,里面存放的是表 CUSTOMERS 的数据。
清单 18. 读取 XML 文件
WebRowSet newWebRS = new WebRowSetImpl();
newWebRS.readXml(new FileReader("CUSTOMERS.xml"));
相比于 CachedRowSet,WebRowSet 就是添加了对 XML 文件读写的支持。它可以将自身数据输出到 XML 文件,也可以读取符合规范的 XML 文件,来填充自己。上段示例代码中已经演示了如何读取 XML 文件。如下示例代码则是生成一个 XML 文件。
清单 19. 生成 XML 文件
WebRowSet webRS = new WebRowSetImpl();
CachedRowSetDemo.fillRowSetWithExecute(webRS); // 输出到XML文件
FileWriter fileWriter = new FileWriter("CUSTOMERS.xml");
webRS.writeXml(fileWriter);
fillRowSetWithExecute(CachedRowSet) 是一个静态方法,它的功能是用表 CUSTOMERS 来填充传入的 CachedRowSet。
应注意的问题
按照规范,当一行被标记为更新时,在输出到 XML 文件时,应使用标签 <modifyRow>,但实际输出为 <currentRow>。
按照规范,当一行中某列被更新时,在输出到 XML 文件时,应使用标签 <updateValue>,但实际输出为 <updateRow>。
按照规范,读取 XML 文件时,如果某行标签为 <deleteRow>,在读到 WebRowSet 中时,该行应被标记为删除,实际读取后,状态丢失。
使用 FilteredRowSet
FilteredRowSet 继承自 WebRowSet。正如它的名字所示,FilteredRowSet 是一个带过滤功能的 RowSet。它的过滤规则在 Predicate 中定义。Predicate 也是 javax.sql.rowset 包下的接口,它定义了三个方法:boolean evaluate(Object value, int column),boolean evaluate(Object value, String columnName),boolean evaluate(RowSet rs)。 前两个方法主要是用来检查新插入行的值是否符合过滤规则,符合,返回 true;否则,返回 false。FilteredRowSet 在新插入行时,会用这个方法来检测。如果不符合,会抛出 SQLException。boolean evaluate(RowSet rs) 这个方法则是用来判断当前 RowSet 里面的所有数据,是否符合过滤规则。FilteredRowSet 在调用有关移动游标的方法时,会使用这个方法进行检测。只要符合过滤规则的数据才会显示出来。下面我们给出了一个非常简单的 Predicate 实现,它的过滤规则是每行第一列的值只有大于 1 的才是有效行。
清单 20. Rang.java
class Range implements Predicate
{
@Override public boolean evaluate(RowSet rs)
{
try {
if (rs.getInt(1) > 1)
{
return true;
}
} catch (SQLException e) { // do nothing }
return false;
}
@Override public boolean evaluate(Object value, int column) throws SQLException { return false; }
@Override public boolean evaluate(Object value, String columnName) throws SQLException
{ return false; }
}
下面这段代码演示了使用上面定义的 class Range 前后的数据变化。
清单 21.
FilteredRowSet filterRS = new FilteredRowSetImpl();
CachedRowSetDemo.fillRowSetWithExecute(filterRS);
System.out.println("/*******Before set filter***********/");
CachedRowSetDemo.printRowSet(filterRS);
System.out.println("\n/*******After set filter***********/");
Range range = new Range(); filterRS.setFilter(range);
CachedRowSetDemo.printRowSet(filterRS);
清单 22. 清单 21 中的代码执行结果
/*******Before set filter***********/
The data in RowSet: 1 Tom Tom is VIP. 2 Jim null
/*******After set filter***********/
The data in RowSet: 2 Jim null
可以看出,在设置了过滤器后,再次打印 FilteredRowSet 中的数据时,由于第一行第一列的值为 1,不符合过滤规则,因此,没有打印出来。上面实现的这个 Predicate 中,用来检测新插入行是否符合过滤规则的方法直接返回 false。这样,在新插入行时,无论插入什么值时,都将抛出 SQLException。
应注意的问题
当设置了过滤器后,FilteredRowSet.absolute(1) 无论何时都返回 false。按照规范,如果第一行值不符合过滤规则,则移到第二行,依次类推,直到找到第一条符合过滤规则的结果行并返回 true,否则,返回 false。
在 JDK 5.0 下,如果 FilteredRowSet 没有设置过滤器,那么在新插入一行时会抛出 NullPointerException。在 JDK 6.0 中,已解决该问题。
使用 JdbcRowSet
JdbcRowSet 是对 ResultSet 的一个简单的封装,让它可以作为一个 JavaBeans 组件来使用。填充 JdbcRowSet 只能通过一种方式,即 execute() 方法。之后可以通过类似于操作 ResultSet 的方法来操作 JdbcRowSet。
清单 23.
JdbcRowSet JdbcRowSet jrs = new JdbcRowSetImpl();
jrs.setCommand(DBCreator.SQL_SELECT_CUSTOMERS);
jrs.setUrl(DBCreator.DERBY_URL);
jrs.execute();
while (jrs.next())
{
for(int i = 1; i <= jrs.getMetaData().getColumnCount(); i++)
{
System.out.print(jrs.getObject(i) + " ");
}
System.out.println();
}