使用 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 对象
- 创建和设置谓词对象
- 使用新的谓词对象设置 FilteredRowSet 对象以进一步过滤数据
- 更新 FilteredRowSet 对象
- 插入或更新行
- 删除所有过滤器,以便所有行都可见
- 删除行
要设置FilteredRowSet
对象中哪些行可见的条件,请定义实现Predicate
接口的类。使用此类创建的对象使用以下内容进行初始化:
- 价值必须下降的范围的高端
- 价值必须下降的范围的低端
- 列的列名或列号,其值必须在由高和低边界设置的值范围内
请注意,值的范围是包含的,这意味着边界处的值包含在范围内。例如,如果范围具有 100 的高值和 50 的低值,则认为值 50 在该范围内。值 49 不是。同样,100 在范围内,但 101 不在。
根据业主想要比较加利福尼亚商店的情况,必须编写一个Predicate
接口的实现,该接口可以过滤位于加利福尼亚州的 Coffee Break 咖啡馆。没有一种正确的方法可以做到这一点,这意味着在编写实现方面存在很大的自由度。例如,您可以根据需要为类及其成员命名,并以任何方式实现构造器和三个计算方法,以实现所需的结果。
列出所有咖啡馆的表格,名为COFFEE_HOUSES
,有数百行。为了使事情更易于管理,本示例使用行少得多的表,这足以说明如何完成过滤。
列STORE_ID
中的值是int
值,其中指示咖啡馆所处的状态等。例如,以 10 开头的值表示该州是加利福尼亚州。以 32 开头的STORE_ID
值表示俄勒冈州,以 33 开头的值表示华盛顿州。
以下类 StateFilter
实现Predicate
接口:
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;
}
}
这是一个非常简单的实现,它检查colName
或colNumber
指定的列中的值,以查看它是否在lo
到hi
的范围内。来自 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
中的第一个元素检查该值。将根据lo
和hi
中的第二个元素检查colNumber
指示的第二列中的值,依此类推。因此,三个数组中的元素数应该相同。以下代码是evaluate(RowSet rs)
方法的实现,对于Filter2
对象,其参数是数组:
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
类型。例如,如果对lo
和hi
使用int
值,则必须将int
值转换为Integer
对象,然后再将其传递给构造器。 String
对象已经是Object
类型,因此您无需转换它们。
FilteredRowSet
接口的参考实现FilteredRowSetImpl
包括一个默认构造器,在下面的代码行中用于创建空FilteredRowSet
对象frs:
。
FilteredRowSet frs = new FilteredRowSetImpl();
实现扩展了BaseRowSet
抽象类,因此frs
对象具有BaseRowSet
中定义的默认属性。这意味着frs
是可滚动的,可更新的,不显示已删除的行,已启用转义处理,依此类推。此外,由于FilteredRowSet
接口是CachedRowSet
,Joinable
和WebRowSet
的子接口,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
对象:
frs.setCommand("SELECT * FROM COFFEE_HOUSES");
frs.setUsername(settings.userName);
frs.setPassword(settings.password);
frs.setUrl(settings.urlString);
以下代码行使用COFFEE_HOUSE
表中存储的数据填充frs
对象:
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
对象来检索加利福尼亚州的咖啡店商店列表。现在,业主希望比较加利福尼亚州的两个城市,旧金山(表中的 SF COFFEE_HOUSES
)和洛杉矶(表中的 LA)的商店。首先要做的是编写一个Predicate
实现,过滤 SF 或 LA 中的商店:
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
当前仅包含商店位于加利福尼亚州的行,因此当过滤器更改为另一个Predicate
对象时,Predicate
对象state
的条件仍然有效。下面的代码将过滤器设置为CityFilter
对象city
。 CityFilter
实现使用数组作为构造器的参数来说明如何完成:
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);
}
}
对于位于加利福尼亚州旧金山或加利福尼亚州洛杉矶的每个商店,输出应包含一行。如果有一行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", "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
对象,你会发现加利福尼亚州旧金山的新咖啡馆有一排,但华盛顿旧金山的商店没有。
所有者可以通过使过滤器无效来在华盛顿添加商店。如果未设置过滤器,则frs
对象中的所有行将再次可见,并且可以将任何位置的存储添加到商店列表中。以下代码行取消设置当前过滤器,有效地使先前在frs
对象上设置的两个Predicate
实现无效。
frs.setFilter(null);
如果店主决定关闭或出售其中一个咖啡休息咖啡馆,店主将希望将其从COFFEE_HOUSES
表中删除。只要行可见,所有者就可以删除表现不佳的咖啡馆的行。
例如,假设刚刚使用参数 null 调用方法setFilter
,则frs
对象上没有设置过滤器。这意味着所有行都是可见的,因此可以删除。但是,在设置StateFilter
对象myStateFilter
后,过滤掉加利福尼亚州以外的任何州,只能删除位于加利福尼亚州的商店。当为frs
对象设置了CityFilter
对象myCityFilter
时,只能删除加利福尼亚州旧金山或加利福尼亚州洛杉矶的咖啡馆,因为它们只在可见的行中。