本教程适用于JDK 8。本页面描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
有关Java SE 9和后续版本中更新的语言功能的摘要,请参见Java语言变化。
有关所有JDK版本的新功能、增强功能以及已删除或弃用选项的信息,请参见JDK发行说明。
FilteredRowSet
对象允许您减少RowSet
对象中可见的行数,以便您只处理与您所做的相关的数据。您可以决定对数据设置何种限制(如何"过滤"数据),然后将该过滤器应用于FilteredRowSet
对象。换句话说,FilteredRowSet
对象只显示符合您设置的限制的数据行。一个始终与其数据源保持连接的JdbcRowSet
对象可以通过向数据源发送只选择您想要查看的列和行的查询来进行这种过滤。查询的WHERE
子句定义了过滤条件。FilteredRowSet
对象提供了一种让离线RowSet
对象进行此过滤的方式,而无需在数据源上执行查询,从而避免了连接到数据源和发送查询的过程。
例如,假设咖啡连锁店Coffee Break在美国各地已经发展到数百家店铺,并且所有店铺都在一个名为COFFEE_HOUSES
的表中列出。店主希望通过一个咖啡店比较应用程序来衡量仅限于加利福尼亚州的店铺的成功,该应用程序不需要对数据库系统进行持久连接。该比较将考虑到销售商品与销售咖啡饮料的盈利能力,以及其他各种成功指标,并按咖啡饮料销售额、商品销售额和总销售额对加利福尼亚州的店铺进行排名。由于COFFEE_HOUSES
表有数百行,如果将被搜索的数据量减少到只有那些STORE_ID
列中值表示加利福尼亚州的行,那么这些比较将更快、更容易进行。
这正是FilteredRowSet
对象所解决的问题,它提供了以下功能:
下面的主题将被介绍:
为了设置 FilteredRowSet
对象中可见的行的条件,您需要定义一个实现了 Predicate
接口的类。使用该类创建的对象将被初始化为包含以下内容:
请注意,值的范围是包含边界的,这意味着边界上的值也包含在范围内。例如,如果范围的上限是100,下限是50,那么50就被认为在范围内,而49不在范围内。同样,100在范围内,而101不在范围内。
根据业主想要比较加利福尼亚州的商店的场景,必须编写一个实现 Predicate
接口的过滤器,以过滤在加利福尼亚州的 Coffee Break 咖啡店。这并没有唯一的正确方法,因此在编写实现时有很大的自由度。例如,您可以自行命名类及其成员,并以任何方式实现构造函数和三个评估方法以达到所需的结果。
列出所有咖啡店的表名为 COFFEE_HOUSES
,其中有数百行。为了使事情更可管理,此示例使用的是行数较少的表,足以演示如何进行过滤。
列 STORE_ID
中的值是一个 int
值,表示咖啡店所在的州,以及其他信息。例如,以 10 开头的值表示该州是加利福尼亚州。以 32 开头的 STORE_ID
值表示俄勒冈州,以 33 开头的值表示华盛顿州。
下面的类 StateFilter
实现了 Predicate
接口:
colName
colNumber
lo
hi
FilteredRowSetSample.java
STORE_ID
StateFilter myStateFilter = new StateFilter(10000, 10999, 1);
请注意,刚刚定义的StateFilter
类只适用于一列。通过将每个参数改为数组,可以使其适用于两列或更多列。例如,Filter
对象的构造函数可以如下所示:
public Filter2(Object [] lo, Object [] hi, Object [] colNumber) { this.lo = lo; this.hi = hi; this.colNumber = colNumber; }
colNumber
对象中的第一个元素表示要将值与lo
的第一个元素和hi
的第一个元素进行比较的第一列。通过colNumber
指示的第二列的值将与lo
的第二个元素和hi
的第二个元素进行比较,依此类推。因此,这三个数组中的元素数量应该相同。下面的代码是一个Filter2
对象的evaluate(RowSet rs)
方法的实现示例,其中参数是数组:
public boolean evaluate(RowSet rs) { CachedRowSet crs = (CachedRowSet)rs; boolean bool1; boolean bool2; for (int i = 0; i < colNumber.length; i++) { if ((rs.getObject(colNumber[i] >= lo [i]) && (rs.getObject(colNumber[i] <= hi[i]) { bool1 = true; } else { bool2 = true; } if (bool2) { return false; } else { return true; } } }
使用Filter2
实现的优点是您可以使用任何Object
类型的参数,并且可以检查一列或多列,而无需编写另一个实现。但是,您必须传递一个Object
类型,这意味着您必须将原始类型转换为其Object
类型。例如,如果您使用一个int
值作为lo
和hi
,则必须在将其传递给构造函数之前将int
值转换为Integer
对象。String
对象已经是Object
类型,因此无需转换。
使用从RowSetProvider
类创建的RowSetFactory
实例来创建一个FilteredRowSet
对象。以下是来自FilteredRowSetSample.java
的示例:
RowSetFactory factory = RowSetProvider.newFactory(); try (FilteredRowSet frs = factory.createFilteredRowSet()) { // ...
与其他断开式 RowSet
对象一样,frs
对象必须从表格数据源中获取数据,该数据源是参考实现中的关系型数据库。以下来自 FilteredRowSetSample.java
的代码片段设置了连接到数据库并执行命令所需的属性。注意,此代码使用 DriverManager
类进行连接,这样做是为了方便。通常,最好使用已经在实现了Java命名和目录接口(JNDI)的命名服务中注册的 DataSource
对象。
RowSetFactory factory = RowSetProvider.newFactory(); try (FilteredRowSet frs = factory.createFilteredRowSet()){ frs.setCommand("SELECT * FROM COFFEE_HOUSES"); frs.setUsername(settings.userName); frs.setPassword(settings.password); frs.setUrl(settings.urlString); frs.execute(); // ...
以下代码行将 frs
对象使用存储在 COFFEE_HOUSES
表中的数据填充:
frs.execute();
execute
方法通过调用 frs
的 RowSetReader
对象在后台执行各种操作,包括创建连接、执行 frs
的命令、使用产生的 ResultSet
对象填充 frs
并关闭连接。请注意,如果表 COFFEE_HOUSES
中的行比 frs
对象一次性能够容纳的行数多,将使用 CachedRowSet
的分页方法。
在这个场景中,咖啡店老板会在办公室里完成前面的任务,然后将存储在 frs
对象中的信息导入或下载到咖啡店比较应用程序中。从现在开始,frs
对象将独立运行,不再连接到数据源。
现在 FilteredRowSet
对象 frs
包含了 Coffee Break 店铺的列表,您可以设置选择条件来缩小在 frs
对象中可见的行数。
以下代码使用之前定义的 StateFilter
类创建对象 myStateFilter
,该对象检查 STORE_ID
列以确定哪些店铺位于加利福尼亚州(如果其ID号在10000和10999之间(包括边界),则表示该店铺位于加利福尼亚州):
StateFilter myStateFilter = new StateFilter(10000, 10999, 1);
以下代码将myStateFilter
设置为frs
的过滤器。
frs.setFilter(myStateFilter);
要进行实际的过滤,您需要调用next
方法,该方法在引用实现中调用您之前实现的Predicate.evaluate
方法的适当版本。
如果返回值为true
,则该行将可见;如果返回值为false
,则该行将不可见。
您可以连续设置多个过滤器。第一次调用setFilter
方法并传递一个Predicate
对象时,您已经应用了该过滤器中的过滤条件。在对每一行调用next
方法后,只有满足过滤器的行才会可见,然后您可以再次调用setFilter
方法,传递一个不同的Predicate
对象。即使一次只设置一个过滤器,但效果是累积应用这两个过滤器。
例如,所有者通过将stateFilter
设置为frs
的Predicate
对象来检索了加利福尼亚州的Coffee Break店列表。现在,所有者想要比较加利福尼亚州的两个城市,旧金山(SF在表COFFEE_HOUSES
中)和洛杉矶(LA在表中)。首先要做的是编写一个Predicate
实现,用于过滤旧金山或洛杉矶的店铺:
public class CityFilter implements Predicate { private String[] cities; private String colName = null; private int colNumber = -1; public CityFilter(String[] citiesArg, String colNameArg) { this.cities = citiesArg; this.colNumber = -1; this.colName = colNameArg; } public CityFilter(String[] citiesArg, int colNumberArg) { this.cities = citiesArg; this.colNumber = colNumberArg; this.colName = null; } public boolean evaluate Object valueArg, String colNameArg) { if (colNameArg.equalsIgnoreCase(this.colName)) { for (int i = 0; i < this.cities.length; i++) { if (this.cities[i].equalsIgnoreCase((String)valueArg)) { return true; } } } return false; } public boolean evaluate(Object valueArg, int colNumberArg) { if (colNumberArg == this.colNumber) { for (int i = 0; i < this.cities.length; i++) { if (this.cities[i].equalsIgnoreCase((String)valueArg)) { return true; } } } return false; } public boolean evaluate(RowSet rs) { if (rs == null) return false; try { for (int i = 0; i < this.cities.length; i++) { String cityName = null; if (this.colNumber > 0) { cityName = (String)rs.getObject(this.colNumber); } else if (this.colName != null) { cityName = (String)rs.getObject(this.colName); } else { return false; } if (cityName.equalsIgnoreCase(cities[i])) { return true; } } } catch (SQLException e) { return false; } return false; } }
下面的代码片段来自FilteredRowSetSample.java
,它设置了新的过滤器并遍历frs
中的行,打印出CITY
列包含SF或LA的行。请注意,frs
目前只包含店铺位于加利福尼亚州的行,因此当过滤器更改为另一个Predicate
对象时,state
对象的条件仍然有效。接下来的代码将过滤器设置为CityFilter
对象city
。 CityFilter
实现使用数组作为构造函数的参数,以说明如何完成此操作:
public void testFilteredRowSet() throws SQLException { StateFilter myStateFilter = new StateFilter(10000, 10999, 1); String[] cityArray = { "SF", "LA" }; CityFilter myCityFilter = new CityFilter(cityArray, 2); RowSetFactory factory = RowSetProvider.newFactory(); try (FilteredRowSet frs = factory.createFilteredRowSet()){ frs.setCommand("SELECT * FROM COFFEE_HOUSES"); frs.setUsername(settings.userName); frs.setPassword(settings.password); frs.setUrl(settings.urlString); frs.execute(); System.out.println("\nBefore filter:"); FilteredRowSetSample.viewTable(this.con); System.out.println("\nSetting state filter:"); frs.beforeFirst(); frs.setFilter(myStateFilter); this.viewFilteredRowSet(frs); System.out.println("\nSetting city filter:"); frs.beforeFirst(); frs.setFilter(myCityFilter); this.viewFilteredRowSet(frs); } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } }
输出应该包含每个店铺位于加利福尼亚州的旧金山或洛杉矶的行。如果CITY
列包含LA并且STORE_ID
列包含40003的行,它将不会包含在列表中,因为在将过滤器设置为state
时已经被过滤掉了。(40003不在10000到10999的范围内。)
您可以对FilteredRowSet
对象进行更改,但前提是该更改不违反当前有效的任何过滤条件。例如,如果新值或新值在现有行中的一个或多个值符合过滤条件,则可以插入新行或更改现有行中的一个或多个值。
假设刚刚开设了两家新的咖啡店,并且所有者希望将它们添加到所有咖啡店的列表中。如果要插入的行不满足当前累积的过滤条件,则将阻止其添加。
frs
对象的当前状态是先设置了StateFilter
对象,然后设置了CityFilter
对象。因此,frs
当前仅显示满足两个过滤器条件的行。同样重要的是,除非满足两个过滤器的条件,否则无法向frs
对象添加行。以下代码片段尝试将两个新行插入frs
对象,其中一个行中的STORE_ID
和CITY
列的值都满足条件,另一个行中STORE_ID
的值不符合过滤器,但CITY
列的值符合:
frs.moveToInsertRow(); frs.updateInt("STORE_ID", 10101); frs.updateString("CITY", "旧金山"); frs.updateLong("COF_SALES", 0); frs.updateLong("MERCH_SALES", 0); frs.updateLong("TOTAL_SALES", 0); frs.insertRow(); frs.updateInt("STORE_ID", 33101); frs.updateString("CITY", "旧金山"); frs.updateLong("COF_SALES", 0); frs.updateLong("MERCH_SALES", 0); frs.updateLong("TOTAL_SALES", 0); frs.insertRow(); frs.moveToCurrentRow();
如果您使用next
方法迭代frs
对象,您会发现旧金山,加利福尼亚州的新咖啡馆有一行,但旧金山,华盛顿州的店没有。
店主可以通过将过滤器设为null来添加华盛顿州的店。没有设置过滤器,frs
对象中的所有行都再次可见,可以将任何位置的店添加到店列表中。以下代码行将取消当前过滤器,有效地使之前在frs
对象上设置的两个Predicate
实现无效。
frs.setFilter(null);
如果店主决定关闭或出售Coffee Break咖啡馆中的一家,店主希望从COFFEE_HOUSES
表中将其删除。只要行可见,店主就可以删除表现不佳的咖啡馆的行。
例如,假设刚刚使用参数null调用了方法setFilter
,在frs
对象上没有设置过滤器。这意味着所有行都可见,因此可以删除它们。然而,当设置了StateFilter
对象myStateFilter
后,过滤掉了除加利福尼亚州以外的任何州,只有位于加利福尼亚州的店可以被删除。当为frs
对象设置了CityFilter
对象myCityFilter
后,只有位于加利福尼亚州的旧金山或洛杉矶的咖啡馆可以被删除,因为它们是唯一可见的行。