使用 FilteredRowSet 对象

原文: https://docs.oracle.com/javase/tutorial/jdbc/basics/filteredrowset.html

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接口:

  1. public class StateFilter implements Predicate {
  2. private int lo;
  3. private int hi;
  4. private String colName = null;
  5. private int colNumber = -1;
  6. public StateFilter(int lo, int hi, int colNumber) {
  7. this.lo = lo;
  8. this.hi = hi;
  9. this.colNumber = colNumber;
  10. }
  11. public StateFilter(int lo, int hi, String colName) {
  12. this.lo = lo;
  13. this.hi = hi;
  14. this.colName = colName;
  15. }
  16. public boolean evaluate(Object value, String columnName) {
  17. boolean evaluation = true;
  18. if (columnName.equalsIgnoreCase(this.colName)) {
  19. int columnValue = ((Integer)value).intValue();
  20. if ((columnValue >= this.lo)
  21. &&
  22. (columnValue <= this.hi)) {
  23. evaluation = true;
  24. } else {
  25. evaluation = false;
  26. }
  27. }
  28. return evaluation;
  29. }
  30. public boolean evaluate(Object value, int columnNumber) {
  31. boolean evaluation = true;
  32. if (this.colNumber == columnNumber) {
  33. int columnValue = ((Integer)value).intValue();
  34. if ((columnValue >= this.lo)
  35. &&
  36. (columnValue <= this.hi)) {
  37. evaluation = true;
  38. } else {
  39. evaluation = false;
  40. }
  41. }
  42. return evaluation;
  43. }
  44. public boolean evaluate(RowSet rs) {
  45. CachedRowSet frs = (CachedRowSet)rs;
  46. boolean evaluation = false;
  47. try {
  48. int columnValue = -1;
  49. if (this.colNumber > 0) {
  50. columnValue = frs.getInt(this.colNumber);
  51. } else if (this.colName != null) {
  52. columnValue = frs.getInt(this.colName);
  53. } else {
  54. return false;
  55. }
  56. if ((columnValue >= this.lo)
  57. &&
  58. (columnValue <= this.hi)) {
  59. evaluation = true;
  60. }
  61. } catch (SQLException e) {
  62. JDBCTutorialUtilities.printSQLException(e);
  63. return false;
  64. } catch (NullPointerException npe) {
  65. System.err.println("NullPointerException caught");
  66. return false;
  67. }
  68. return evaluation;
  69. }
  70. }

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

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

请注意,刚刚定义的StateFilter类适用于一列。通过使每个参数数组而不是单个值,可以将它应用于两个或更多列。例如,Filter对象的构造器可能如下所示:

  1. public Filter2(Object [] lo, Object [] hi, Object [] colNumber) {
  2. this.lo = lo;
  3. this.hi = hi;
  4. this.colNumber = colNumber;
  5. }

colNumber对象中的第一个元素给出第一列,其中将根据lo中的第一个元素和hi中的第一个元素检查该值。将根据lohi中的第二个元素检查colNumber指示的第二列中的值,依此类推。因此,三个数组中的元素数应该相同。以下代码是evaluate(RowSet rs)方法的实现,对于Filter2对象,其参数是数组:

  1. public boolean evaluate(RowSet rs) {
  2. CachedRowSet crs = (CachedRowSet)rs;
  3. boolean bool1;
  4. boolean bool2;
  5. for (int i = 0; i < colNumber.length; i++) {
  6. if ((rs.getObject(colNumber[i] >= lo [i]) &&
  7. (rs.getObject(colNumber[i] <= hi[i]) {
  8. bool1 = true;
  9. } else {
  10. bool2 = true;
  11. }
  12. if (bool2) {
  13. return false;
  14. } else {
  15. return true;
  16. }
  17. }
  18. }

使用Filter2实现的优点是您可以使用任何Object类型的参数,并且可以检查一列或多列而无需编写其他实现。但是,您必须传递Object类型,这意味着您必须将基本类型转换为其Object类型。例如,如果对lohi使用int值,则必须将int值转换为Integer对象,然后再将其传递给构造器。 String对象已经是Object类型,因此您无需转换它们。

FilteredRowSet接口的参考实现FilteredRowSetImpl包括一个默认构造器,在下面的代码行中用于创建空FilteredRowSet对象frs:

  1. FilteredRowSet frs = new FilteredRowSetImpl();

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

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

您可以使用从RowSetProvider类创建的RowSetFactory实例来创建FilteredRowSet对象。有关详细信息,请参阅使用中的 RowSetFactory 接口使用 JdbcRowSet 对象。

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

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

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

  1. frs.execute();

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

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

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

以下代码行使用先前定义的StateFilter类来创建对象myStateFilter,该对象检查列STORE_ID以确定哪些商店位于加利福尼亚(如果商店的 ID 号介于 10000 和 10999 之间,则商店位于加利福尼亚州) , 包括的):

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

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

  1. frs.setFilter(myStateFilter);

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

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

您可以串行设置多个过滤器。第一次调用方法setFilter并将其传递给Predicate对象时,您已在该过滤器中应用了过滤条件。在每行调用方法next后,只显示那些满足过滤器的行,你可以再次调用setFilter,传递一个不同的Predicate对象。即使一次只设置一个过滤器,效果是两个过滤器都累积应用。

例如,所有者通过将stateFilter设置为frsPredicate对象来检索加利福尼亚州的咖啡店商店列表。现在,业主希望比较加利福尼亚州的两个城市,旧金山(表中的 SF COFFEE_HOUSES)和洛杉矶(表中的 LA)的商店。首先要做的是编写一个Predicate实现,过滤 SF 或 LA 中的商店:

  1. public class CityFilter implements Predicate {
  2. private String[] cities;
  3. private String colName = null;
  4. private int colNumber = -1;
  5. public CityFilter(String[] citiesArg, String colNameArg) {
  6. this.cities = citiesArg;
  7. this.colNumber = -1;
  8. this.colName = colNameArg;
  9. }
  10. public CityFilter(String[] citiesArg, int colNumberArg) {
  11. this.cities = citiesArg;
  12. this.colNumber = colNumberArg;
  13. this.colName = null;
  14. }
  15. public boolean evaluate Object valueArg, String colNameArg) {
  16. if (colNameArg.equalsIgnoreCase(this.colName)) {
  17. for (int i = 0; i < this.cities.length; i++) {
  18. if (this.cities[i].equalsIgnoreCase((String)valueArg)) {
  19. return true;
  20. }
  21. }
  22. }
  23. return false;
  24. }
  25. public boolean evaluate(Object valueArg, int colNumberArg) {
  26. if (colNumberArg == this.colNumber) {
  27. for (int i = 0; i < this.cities.length; i++) {
  28. if (this.cities[i].equalsIgnoreCase((String)valueArg)) {
  29. return true;
  30. }
  31. }
  32. }
  33. return false;
  34. }
  35. public boolean evaluate(RowSet rs) {
  36. if (rs == null) return false;
  37. try {
  38. for (int i = 0; i < this.cities.length; i++) {
  39. String cityName = null;
  40. if (this.colNumber > 0) {
  41. cityName = (String)rs.getObject(this.colNumber);
  42. } else if (this.colName != null) {
  43. cityName = (String)rs.getObject(this.colName);
  44. } else {
  45. return false;
  46. }
  47. if (cityName.equalsIgnoreCase(cities[i])) {
  48. return true;
  49. }
  50. }
  51. } catch (SQLException e) {
  52. return false;
  53. }
  54. return false;
  55. }
  56. }

来自 FilteredRowSetSample 的以下代码片段设置新过滤器并遍历frs中的行,打印出CITY列包含 SF 或 LA 的行。请注意,frs当前仅包含商店位于加利福尼亚州的行,因此当过滤器更改为另一个Predicate对象时,Predicate对象state的条件仍然有效。下面的代码将过滤器设置为CityFilter对象cityCityFilter实现使用数组作为构造器的参数来说明如何完成:

  1. public void testFilteredRowSet() {
  2. FilteredRowSet frs = null;
  3. StateFilter myStateFilter = new StateFilter(10000, 10999, 1);
  4. String[] cityArray = { "SF", "LA" };
  5. CityFilter myCityFilter = new CityFilter(cityArray, 2);
  6. try {
  7. frs = new FilteredRowSetImpl();
  8. frs.setCommand("SELECT * FROM COFFEE_HOUSES");
  9. frs.setUsername(settings.userName);
  10. frs.setPassword(settings.password);
  11. frs.setUrl(settings.urlString);
  12. frs.execute();
  13. System.out.println("\nBefore filter:");
  14. FilteredRowSetSample.viewTable(this.con);
  15. System.out.println("\nSetting state filter:");
  16. frs.beforeFirst();
  17. frs.setFilter(myStateFilter);
  18. this.viewFilteredRowSet(frs);
  19. System.out.println("\nSetting city filter:");
  20. frs.beforeFirst();
  21. frs.setFilter(myCityFilter);
  22. this.viewFilteredRowSet(frs);
  23. } catch (SQLException e) {
  24. JDBCTutorialUtilities.printSQLException(e);
  25. }
  26. }

对于位于加利福尼亚州旧金山或加利福尼亚州洛杉矶的每个商店,输出应包含一行。如果有一行CITY列包含 LA 而STORE_ID列包含 40003,则它不会包含在列表中,因为当过滤器设置为state时它已被过滤掉。 (40003 不在 10000 到 10999 的范围内。)

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

假设两个新的咖啡休息咖啡馆刚刚开业,店主想将它们添加到所有咖啡馆的名单中。如果要插入的行不符合有效的累积过滤条件,则将阻止添加该行。

frs对象的当前状态是设置了StateFilter对象,然后设置了CityFilter对象。因此,frs目前仅显示满足两个过滤器条件的那些行。同样重要的是,除非满足两个过滤器的条件,否则不能向frs对象添加行。以下代码片段尝试在frs对象中插入两个新行,其中一行STORE_IDCITY列中的值均满足条件,另一行中STORE_ID中的值为不通过过滤器,但CITY列中的值执行:

  1. frs.moveToInsertRow();
  2. frs.updateInt("STORE_ID", 10101);
  3. frs.updateString("CITY", "SF");
  4. frs.updateLong("COF_SALES", 0);
  5. frs.updateLong("MERCH_SALES", 0);
  6. frs.updateLong("TOTAL_SALES", 0);
  7. frs.insertRow();
  8. frs.updateInt("STORE_ID", 33101);
  9. frs.updateString("CITY", "SF");
  10. frs.updateLong("COF_SALES", 0);
  11. frs.updateLong("MERCH_SALES", 0);
  12. frs.updateLong("TOTAL_SALES", 0);
  13. frs.insertRow();
  14. frs.moveToCurrentRow();

如果你使用方法next迭代frs对象,你会发现加利福尼亚州旧金山的新咖啡馆有一排,但华盛顿旧金山的商店没有。

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

  1. frs.setFilter(null);

如果店主决定关闭或出售其中一个咖啡休息咖啡馆,店主将希望将其从COFFEE_HOUSES表中删除。只要行可见,所有者就可以删除表现不佳的咖啡馆的行。

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