使用 FilteredRowSet 对象

FilteredRowSet对象使您可以减少RowSet对象中可见的行数,以便仅使用与您正在执行的操作有关的数据。您决定要对数据设置哪些限制(如何“过滤”数据)并将该过滤器应用于FilteredRowSet对象。换句话说,FilteredRowSet对象仅显示符合您设置的限制的数据行。始终与数据源保持连接的JdbcRowSet对象可以通过查询数据源(仅选择要查看的列和行)来进行此过滤。查询的WHERE子句定义了过滤条件。 FilteredRowSet对象为断开连接的RowSet对象提供了一种执行此筛选的方法,而不必在数据源上执行查询,从而避免了必须与数据源构建连接并向其发送查询。

例如,假设咖啡屋的 Coffee Break 连锁店在美国各地已 Developing 到数百家 Store,并且所有这些 Store 都列在名为COFFEE_HOUSES的表中。所有者希望使用不需要比较数据库系统的持久连接的咖啡馆比较应用程序来衡量仅加利福尼亚 StateStore 的成功率。该比较将着眼于销售商品与销售咖啡饮料的获利能力以及各种其他成功衡量标准,并将根据咖啡饮料销售,商品销售和总销售对加利福尼亚的 Store 进行排名。由于表COFFEE_HOUSES具有数百行,因此,如果将要搜索的数据量减少到仅列STORE_ID中的值表示加利福尼亚的那些行,则这些比较将更快,更容易。

FilteredRowSet对象正是通过提供以下功能来解决的问题:

  • 能够根据设置的标准限制可见的行

  • 能够选择可见数据而无需连接到数据源

涵盖以下主题:

在谓词对象中定义过滤条件

若要设置FilteredRowSet对象中的行可见的条件,请定义一个实现Predicateinterface的类。用此类初始化的对象使用以下初始化:

  • 值必须落入的范围的高端

  • 值必须落入的范围的下限

  • 列的名称或列号,其值必须在上下边界设置的值的范围内

注意,值的范围是包括端值的,这意味着该范围内包括边界处的值。例如,如果范围的上限为 100,下限为 50,则认为值 50 在该范围内。值 49 不是。同样,100 在该范围内,但 101 不在该范围内。

与所有者要比较加利福尼亚 Store 的情况相一致,必须编写Predicateinterface的实现,该interface可以过滤位于加利福尼亚的 Coffee Break 咖啡屋。没有正确的方法来执行此操作,这意味着实现的编写方式有很大的自由度。例如,您可以根据需要命名类及其成员,并以实现所需结果的任何方式实现构造函数和三个评估方法。

列出所有咖啡馆的表名为COFFEE_HOUSES,有数百行。为了使事情更易于 管理,本示例使用的表中的行要少得多,这足以演示如何进行过滤。

STORE_ID列中的值是int值,除其他事项外,该值指示咖啡馆所在的状态。以 10 开头的值表示该 State 为加利福尼亚 State。以 32 开头的STORE_ID值表示俄勒冈 State,以 33 开头的_3 值表示华盛顿 State。

以下类StateFilter实现Predicateinterface:

public class StateFilter implements Predicate {

    private int lo;
    private int hi;
    private String colName = null;
    private int colNumber = -1;

    public StateFilter(int lo, int hi, int colNumber) {
        this.lo = lo;
        this.hi = hi;
        this.colNumber = colNumber;
    }

    public StateFilter(int lo, int hi, String colName) {
        this.lo = lo;
        this.hi = hi;
        this.colName = colName;
    }

    public boolean evaluate(Object value, String columnName) {
        boolean evaluation = true;
        if (columnName.equalsIgnoreCase(this.colName)) {
            int columnValue = ((Integer)value).intValue();
            if ((columnValue >= this.lo)
                &&
                (columnValue <= this.hi)) {
                evaluation = true;
            } else {
                evaluation = false;
            }
        }
        return evaluation;
    }

    public boolean evaluate(Object value, int columnNumber) {

        boolean evaluation = true;

        if (this.colNumber == columnNumber) {
            int columnValue = ((Integer)value).intValue();
            if ((columnValue >= this.lo)
                &&
                (columnValue <= this.hi)) {
                evaluation = true;
            } else {
                evaluation = false;
            }
        }
        return evaluation;
    }

    public boolean evaluate(RowSet rs) {
    
        CachedRowSet frs = (CachedRowSet)rs;
        boolean evaluation = false;

        try {
            int columnValue = -1;

            if (this.colNumber > 0) {
                columnValue = frs.getInt(this.colNumber);
            } else if (this.colName != null) {
                columnValue = frs.getInt(this.colName);
            } else {
                return false;
            }

            if ((columnValue >= this.lo)
                &&
                (columnValue <= this.hi)) {
                evaluation = true;
            }
        } catch (SQLException e) {
            JDBCTutorialUtilities.printSQLException(e);
            return false;
        } catch (NullPointerException npe) {
            System.err.println("NullPointerException caught");
            return false;
        }
        return evaluation;
    }
}

这是一个非常简单的实现,它检查colNamecolNumber指定的列中的值,以查看该值是否在lohi的范围内(包括两端)。下面的代码行来自FilteredRowSetSample,创建一个过滤器,该过滤器仅允许STORE_ID列值表示 10000 到 10999 之间的值(表示加利福尼亚位置)的行:

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中的第一个元素检查值。将对照lohi中的第二个元素检查colNumber指示的第二列中的值,依此类推。因此,三个数组中的元素数应相同。以下代码是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类型。例如,如果对lohi使用int值,则必须先将int值转换为Integer对象,然后再将其传递给构造函数。 String对象已经是Object类型,因此您不必转换它们。

创建 FilteredRowSet 对象

FilteredRowSetinterfaceFilteredRowSetImpl的参考实现包括默认的构造函数,该代码在以下代码行中用于创建空的FilteredRowSet对象frs:

FilteredRowSet frs = new FilteredRowSetImpl();

该实现扩展了BaseRowSet抽象类,因此frs对象具有BaseRowSet中定义的默认属性。这意味着frs是可滚动的,可更新的,不显示已删除的行,已启用转义处理等等。另外,由于FilteredRowSetinterface是CachedRowSetJoinableWebRowSet的子interface,因此frs对象具有各自的功能。它可以用作断开连接的RowSet对象,可以是JoinRowSet对象的一部分,并且可以以 XML 格式读取和写入自身。

注意 :或者,您可以使用 JDBC 驱动程序的WebRowSet实现中的构造函数。但是,RowSetinterface的实现将与参考实现不同。这些实现将具有不同的名称和构造函数。例如,WebRowSetinterface的 Oracle JDBC 驱动程序实现称为oracle.jdbc.rowset.OracleWebRowSet

您可以使用从RowSetProvider类创建的RowSetFactory实例来创建FilteredRowSet对象。有关更多信息,请参见使用 JdbcRowSet 对象中的使用 RowSetFactory interface

像其他断开连接的RowSet对象一样,frs对象必须使用来自表格数据源的数据填充自身,该数据源是参考实现中的关系数据库。 FilteredRowSetSample中的以下代码片段设置了连接数据库以执行其命令所需的属性。请注意,此代码使用DriverManager类进行连接,此操作是为了方便起见。通常,最好使用已在实现 Java 命名和目录interface(JNDI)的命名服务中注册的DataSource对象:

frs.setCommand("SELECT * FROM COFFEE_HOUSES");
frs.setUsername(settings.userName);
frs.setPassword(settings.password);
frs.setUrl(settings.urlString);

下面的代码行使用存储在COFFEE_HOUSE表中的数据填充frs对象:

frs.execute();

方法execute通过调用frsRowSetReader对象在后台执行各种操作,该对象创建连接,执行frs的命令,使用产生的ResultSet对象的数据填充frs并关闭连接。请注意,如果表COFFEE_HOUSES的行数一次超过frs对象可以在内存中保留的行数,则将使用CachedRowSet分页方法。

在这种情况下,Coffee Break 所有者将在办公室中完成上述任务,然后将存储在frs对象中的信息导入或下载到咖啡馆比较应用程序。从现在开始,frs对象将独立运行,而无需连接到数据源。

创建和设置谓词对象

现在FilteredRowSet对象frs包含 Coffee Break 场所列表,您可以设置选择标准以缩小frs对象中可见的行数。

以下代码行使用先前定义的StateFilter类创建对象myStateFilter,该对象检查列STORE_ID以确定哪些 Store 在加利福尼亚 State(如果其 ID 号在 10000 和 10999(含)之间,则 Store 在加利福尼亚 State):

StateFilter myStateFilter = new StateFilter(10000, 10999, 1);

下一行将myStateFilter设置为frs的过滤器。

frs.setFilter(myStateFilter);

要进行实际的过滤,请调用方法next,该方法在参考实现中将调用先前实现的Predicate.evaluate方法的相应版本。

如果返回值为true,则该行将可见;否则,该行将变为可见。如果返回值为false,则该行将不可见。

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

您可以依次设置多个过滤器。首次调用方法setFilter并将其传递给Predicate对象时,已在该过滤器中应用了过滤条件。在每行调用方法next(仅使满足过滤条件的那些行可见)之后,您可以再次调用setFilter,并将其传递给另一个Predicate对象。即使一次仅设置一个过滤器,其效果是两个过滤器都累加应用。

例如,所有者通过将stateFilter设置为frsPredicate对象,检索了加利福尼亚 State 的 Coffee BreakStore 列表。现在,所有者想要比较两个加利福尼亚城市的 Store:旧金山(表COFFEE_HOUSES中的 SF)和洛杉矶(表中的 LA)。首先要做的是编写一个Predicate实现,以过滤 SF 或 LA 中的 Store:

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的代码片段设置了新的过滤器,并迭代了frs中的行,并打印出CITY列包含 SF 或 LA 的行。请注意,frs当前仅包含存储在加利福尼亚 State 的行,因此,当将过滤器更改为另一个Predicate对象时,Predicate对象state的条件仍然有效。后面的代码将过滤器设置为CityFilter对象cityCityFilter实现使用数组作为构造函数的参数来说明如何实现:

public void testFilteredRowSet() {

    FilteredRowSet frs = null;
    StateFilter myStateFilter = new StateFilter(10000, 10999, 1);
    String[] cityArray = { "SF", "LA" };

    CityFilter myCityFilter = new CityFilter(cityArray, 2);

    try {
        frs = new FilteredRowSetImpl();

        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);
    }
}

对于位于加利福尼亚 State 旧金山或加利福尼亚 State 洛杉矶的每个 Store,输出应包含一行。如果存在CITY列包含 LA 且STORE_ID列包含 40003 的行,则该行将不包含在列表中,因为在将过滤器设置为state时已将其过滤掉。 (40003 不在 10000 到 10999 的范围内.)

更新 FilteredRowSet 对象

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

插入或更新行

假设有两个新的 Coffee Break 咖啡屋刚刚开放,并且所有者希望将它们添加到所有咖啡屋的列表中。如果要插入的行不符合有效的累积过滤条件,则将阻止添加该行。

frs对象的当前状态是先设置StateFilter对象,然后再设置CityFilter对象。结果,frs当前仅使满足两个过滤器条件的那些行可见。同样重要的是,除非满足两个过滤器的条件,否则您不能向frs对象添加行。下面的代码片段try将两个新行插入frs对象中,其中一行STORE_IDCITY列中的值均符合条件,而另一行中STORE_ID中的值未通过过滤器,但其中的值通过CITY列执行以下操作:

frs.moveToInsertRow();
frs.updateInt("STORE_ID", 10101);
frs.updateString("CITY", "SF");
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", "SF");
frs.updateLong("COF_SALES", 0);
frs.updateLong("MERCH_SALES", 0);
frs.updateLong("TOTAL_SALES", 0);
frs.insertRow();
frs.moveToCurrentRow();

如果要使用方法next遍历frs对象,则会在加利福尼亚 State 旧金山的新咖啡馆找到一行,而在华盛顿旧金山的 Store 找不到一行。

删除所有过滤器,以便显示所有行

所有者可以通过取消过滤器来在华盛顿添加 Store。如果没有设置过滤器,则frs对象中的所有行将再次可见,并且可以将任何位置的 Store 添加到 Store 列表中。以下代码行取消当前过滤器的设置,从而有效地使先前在frs对象上设置的Predicate实现无效。

frs.setFilter(null);

Deleting Rows

如果所有者决定关闭或出售其中一个 Coffee Break 咖啡馆,则所有者将要从COFFEE_HOUSES表中将其删除。所有者可以删除效果欠佳的咖啡屋的行,只要该行可见即可。

例如,假设刚刚使用参数 null 调用了方法setFilter,则在frs对象上未设置任何过滤器。这意味着所有行都是可见的,因此可以删除。但是,在设置了StateFilter对象myStateFilter并将其滤除加利福尼亚 State 以外的任何 State 之后,只能删除位于加利福尼亚的 Store。为frs对象设置CityFilter对象myCityFilter时,只能删除位于加利福尼亚 State 旧金山或加利福尼亚洛杉矶的咖啡馆,因为它们仅在可见的行中。