使用 FilteredRowSet 对象
FilteredRowSet
对象使您可以减少RowSet
对象中可见的行数,以便仅使用与您正在执行的操作有关的数据。您决定要对数据设置哪些限制(如何“过滤”数据)并将该过滤器应用于FilteredRowSet
对象。换句话说,FilteredRowSet
对象仅显示符合您设置的限制的数据行。始终与数据源保持连接的JdbcRowSet
对象可以通过查询数据源(仅选择要查看的列和行)来进行此过滤。查询的WHERE
子句定义了过滤条件。 FilteredRowSet
对象为断开连接的RowSet
对象提供了一种执行此筛选的方法,而不必在数据源上执行查询,从而避免了必须与数据源构建连接并向其发送查询。
例如,假设咖啡屋的 Coffee Break 连锁店在美国各地已 Developing 到数百家 Store,并且所有这些 Store 都列在名为COFFEE_HOUSES
的表中。所有者希望使用不需要比较数据库系统的持久连接的咖啡馆比较应用程序来衡量仅加利福尼亚 StateStore 的成功率。该比较将着眼于销售商品与销售咖啡饮料的获利能力以及各种其他成功衡量标准,并将根据咖啡饮料销售,商品销售和总销售对加利福尼亚的 Store 进行排名。由于表COFFEE_HOUSES
具有数百行,因此,如果将要搜索的数据量减少到仅列STORE_ID
中的值表示加利福尼亚的那些行,则这些比较将更快,更容易。
FilteredRowSet
对象正是通过提供以下功能来解决的问题:
-
能够根据设置的标准限制可见的行
-
能够选择可见数据而无需连接到数据源
涵盖以下主题:
在谓词对象中定义过滤条件
若要设置FilteredRowSet
对象中的行可见的条件,请定义一个实现Predicate
interface的类。用此类初始化的对象使用以下初始化:
-
值必须落入的范围的高端
-
值必须落入的范围的下限
-
列的名称或列号,其值必须在上下边界设置的值的范围内
注意,值的范围是包括端值的,这意味着该范围内包括边界处的值。例如,如果范围的上限为 100,下限为 50,则认为值 50 在该范围内。值 49 不是。同样,100 在该范围内,但 101 不在该范围内。
与所有者要比较加利福尼亚 Store 的情况相一致,必须编写Predicate
interface的实现,该interface可以过滤位于加利福尼亚的 Coffee Break 咖啡屋。没有正确的方法来执行此操作,这意味着实现的编写方式有很大的自由度。例如,您可以根据需要命名类及其成员,并以实现所需结果的任何方式实现构造函数和三个评估方法。
列出所有咖啡馆的表名为COFFEE_HOUSES
,有数百行。为了使事情更易于 管理,本示例使用的表中的行要少得多,这足以演示如何进行过滤。
STORE_ID
列中的值是int
值,除其他事项外,该值指示咖啡馆所在的状态。以 10 开头的值表示该 State 为加利福尼亚 State。以 32 开头的STORE_ID
值表示俄勒冈 State,以 33 开头的_3 值表示华盛顿 State。
以下类StateFilter实现Predicate
interface:
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
指示的第二列中的值,依此类推。因此,三个数组中的元素数应相同。以下代码是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
类型。例如,如果对lo
和hi
使用int
值,则必须先将int
值转换为Integer
对象,然后再将其传递给构造函数。 String
对象已经是Object
类型,因此您不必转换它们。
创建 FilteredRowSet 对象
FilteredRowSet
interfaceFilteredRowSetImpl
的参考实现包括默认的构造函数,该代码在以下代码行中用于创建空的FilteredRowSet
对象frs:
。
FilteredRowSet frs = new FilteredRowSetImpl();
该实现扩展了BaseRowSet
抽象类,因此frs
对象具有BaseRowSet
中定义的默认属性。这意味着frs
是可滚动的,可更新的,不显示已删除的行,已启用转义处理等等。另外,由于FilteredRowSet
interface是CachedRowSet
,Joinable
和WebRowSet
的子interface,因此frs
对象具有各自的功能。它可以用作断开连接的RowSet
对象,可以是JoinRowSet
对象的一部分,并且可以以 XML 格式读取和写入自身。
注意 :或者,您可以使用 JDBC 驱动程序的WebRowSet
实现中的构造函数。但是,RowSet
interface的实现将与参考实现不同。这些实现将具有不同的名称和构造函数。例如,WebRowSet
interface的 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
通过调用frs
的RowSetReader
对象在后台执行各种操作,该对象创建连接,执行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
设置为frs
的Predicate
对象,检索了加利福尼亚 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
对象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);
}
}
对于位于加利福尼亚 State 旧金山或加利福尼亚 State 洛杉矶的每个 Store,输出应包含一行。如果存在CITY
列包含 LA 且STORE_ID
列包含 40003 的行,则该行将不包含在列表中,因为在将过滤器设置为state
时已将其过滤掉。 (40003 不在 10000 到 10999 的范围内.)
更新 FilteredRowSet 对象
您可以对FilteredRowSet
对象进行更改,但前提是该更改不违反当前有效的任何过滤条件。例如,如果一个或多个新值在过滤条件之内,则可以插入新行或更改现有行中的一个或多个值。
插入或更新行
假设有两个新的 Coffee Break 咖啡屋刚刚开放,并且所有者希望将它们添加到所有咖啡屋的列表中。如果要插入的行不符合有效的累积过滤条件,则将阻止添加该行。
frs
对象的当前状态是先设置StateFilter
对象,然后再设置CityFilter
对象。结果,frs
当前仅使满足两个过滤器条件的那些行可见。同样重要的是,除非满足两个过滤器的条件,否则您不能向frs
对象添加行。下面的代码片段try将两个新行插入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
对象,则会在加利福尼亚 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 旧金山或加利福尼亚洛杉矶的咖啡馆,因为它们仅在可见的行中。