文档

Java™ 教程
隐藏目录
使用 FilteredRowSet 对象
路径: JDBC数据库访问
课程: JDBC基础知识

使用FilteredRowSet对象

FilteredRowSet对象允许您减少RowSet对象中可见的行数,以便您只处理与您所做的相关的数据。您可以决定对数据设置何种限制(如何"过滤"数据),然后将该过滤器应用于FilteredRowSet对象。换句话说,FilteredRowSet对象只显示符合您设置的限制的数据行。一个始终与其数据源保持连接的JdbcRowSet对象可以通过向数据源发送只选择您想要查看的列和行的查询来进行这种过滤。查询的WHERE子句定义了过滤条件。FilteredRowSet对象提供了一种让离线RowSet对象进行此过滤的方式,而无需在数据源上执行查询,从而避免了连接到数据源和发送查询的过程。

例如,假设咖啡连锁店Coffee Break在美国各地已经发展到数百家店铺,并且所有店铺都在一个名为COFFEE_HOUSES的表中列出。店主希望通过一个咖啡店比较应用程序来衡量仅限于加利福尼亚州的店铺的成功,该应用程序不需要对数据库系统进行持久连接。该比较将考虑到销售商品与销售咖啡饮料的盈利能力,以及其他各种成功指标,并按咖啡饮料销售额、商品销售额和总销售额对加利福尼亚州的店铺进行排名。由于COFFEE_HOUSES表有数百行,如果将被搜索的数据量减少到只有那些STORE_ID列中值表示加利福尼亚州的行,那么这些比较将更快、更容易进行。

这正是FilteredRowSet对象所解决的问题,它提供了以下功能:

下面的主题将被介绍:

在 Predicate 对象中定义过滤条件

为了设置 FilteredRowSet 对象中可见的行的条件,您需要定义一个实现了 Predicate 接口的类。使用该类创建的对象将被初始化为包含以下内容:

请注意,值的范围是包含边界的,这意味着边界上的值也包含在范围内。例如,如果范围的上限是100,下限是50,那么50就被认为在范围内,而49不在范围内。同样,100在范围内,而101不在范围内。

根据业主想要比较加利福尼亚州的商店的场景,必须编写一个实现 Predicate 接口的过滤器,以过滤在加利福尼亚州的 Coffee Break 咖啡店。这并没有唯一的正确方法,因此在编写实现时有很大的自由度。例如,您可以自行命名类及其成员,并以任何方式实现构造函数和三个评估方法以达到所需的结果。

列出所有咖啡店的表名为 COFFEE_HOUSES,其中有数百行。为了使事情更可管理,此示例使用的是行数较少的表,足以演示如何进行过滤。

STORE_ID 中的值是一个 int 值,表示咖啡店所在的州,以及其他信息。例如,以 10 开头的值表示该州是加利福尼亚州。以 32 开头的 STORE_ID 值表示俄勒冈州,以 33 开头的值表示华盛顿州。

下面的类 StateFilter 实现了 Predicate 接口:

colNamecolNumberlohiFilteredRowSetSample.javaSTORE_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值作为lohi,则必须在将其传递给构造函数之前将int值转换为Integer对象。String对象已经是Object类型,因此无需转换。

创建FilteredRowSet对象

使用从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 方法通过调用 frsRowSetReader 对象在后台执行各种操作,包括创建连接、执行 frs 的命令、使用产生的 ResultSet 对象填充 frs 并关闭连接。请注意,如果表 COFFEE_HOUSES 中的行比 frs 对象一次性能够容纳的行数多,将使用 CachedRowSet 的分页方法。

在这个场景中,咖啡店老板会在办公室里完成前面的任务,然后将存储在 frs 对象中的信息导入或下载到咖啡店比较应用程序中。从现在开始,frs 对象将独立运行,不再连接到数据源。

创建和设置 Predicate 对象

现在 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,则该行将不可见。

使用新的Predicate对象设置FilteredRowSet对象以进一步过滤数据

您可以连续设置多个过滤器。第一次调用setFilter方法并传递一个Predicate对象时,您已经应用了该过滤器中的过滤条件。在对每一行调用next方法后,只有满足过滤器的行才会可见,然后您可以再次调用setFilter方法,传递一个不同的Predicate对象。即使一次只设置一个过滤器,但效果是累积应用这两个过滤器。

例如,所有者通过将stateFilter设置为frsPredicate对象来检索了加利福尼亚州的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对象cityCityFilter实现使用数组作为构造函数的参数,以说明如何完成此操作:

  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对象

您可以对FilteredRowSet对象进行更改,但前提是该更改不违反当前有效的任何过滤条件。例如,如果新值或新值在现有行中的一个或多个值符合过滤条件,则可以插入新行或更改现有行中的一个或多个值。

插入或更新行

假设刚刚开设了两家新的咖啡店,并且所有者希望将它们添加到所有咖啡店的列表中。如果要插入的行不满足当前累积的过滤条件,则将阻止其添加。

frs对象的当前状态是先设置了StateFilter对象,然后设置了CityFilter对象。因此,frs当前仅显示满足两个过滤器条件的行。同样重要的是,除非满足两个过滤器的条件,否则无法向frs对象添加行。以下代码片段尝试将两个新行插入frs对象,其中一个行中的STORE_IDCITY列的值都满足条件,另一个行中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后,只有位于加利福尼亚州的旧金山或洛杉矶的咖啡馆可以被删除,因为它们是唯一可见的行。


上一页:使用JoinRowSet对象
下一页:使用WebRowSet对象