Using CachedRowSetObjects
CachedRowSet
对象的特殊之处在于它可以在不连接到其数据源的情况下运行,即它是断开连接的RowSet
对象。它之所以得名,是因为它将数据存储(缓存)在内存中,这样它就可以对自己的数据进行操作,而不是对数据库中存储的数据进行操作。
CachedRowSet
interface是所有断开连接的RowSet
对象的superinterface,因此此处演示的所有内容也适用于WebRowSet
,JoinRowSet
和FilteredRowSet
对象。
请注意,尽管CachedRowSet
对象(以及从其派生的RowSet
对象)的数据源几乎总是一个关系数据库,但是CachedRowSet
对象能够从任何以表格格式存储其数据的数据源中获取数据。例如,平面文件或电子表格可能是数据源。当实现用于断开连接的RowSet
对象的RowSetReader
对象以从此类数据源读取数据时,这是正确的。 CachedRowSet
interface的参考实现有一个RowSetReader
对象,该对象从关系数据库中读取数据,因此在本教程中,数据源始终是数据库。
涵盖以下主题:
设置 CachedRowSet 对象
设置CachedRowSet
对象涉及以下内容:
创建 CachedRowSet 对象
您可以通过以下不同方式创建新的CachedRowSet
对象:
-
使用从
RowSetProvider
类创建的RowSetFactory
实例:有关更多信息,请参见使用 JdbcRowSet 对象中的使用 RowSetFactory interface。
注意 :或者,您可以使用 JDBC 驱动程序的CachedRowSet
实现中的构造函数。但是,RowSet
interface的实现将与参考实现不同。这些实现将具有不同的名称和构造函数。例如,CachedRowSet
interface的 Oracle JDBC 驱动程序实现称为oracle.jdbc.rowset.OracleCachedRowSet
。
使用默认构造函数
创建CachedRowSet
对象的方法之一是调用参考实现中定义的默认构造函数,如以下代码行所示:
CachedRowSet crs = new CachedRowSetImpl();
crs
对象的属性与JdbcRowSet
对象首次创建时具有相同的默认值。此外,已为其分配了默认SyncProvider
实现RIOptimisticProvider
的实例。
SyncProvider
对象提供RowSetReader
对象(读取器)和RowSetWriter
对象(写入器),断开连接的RowSet
对象需要从其数据源读取数据或将数据写回到其数据源。稍后在Reader 做什么和Writer 做什么部分中说明 Reader 和 Writer 的工作。要记住的一件事是,Reader 和 Writer 完全是在后台工作,因此对他们如何工作的解释仅供参考。对 Reader 和 Writer 有一些了解,应该可以帮助您了解CachedRowSet
interface中定义的某些方法在后台所做的工作。
设置 CachedRowSet 属性
通常,属性的默认值可以保持原样,但是您可以通过调用适当的 setter 方法来更改属性的值。您必须自行设置一些没有默认值的属性。
为了获取数据,断开连接的RowSet
对象必须能够连接到数据源,并具有一些选择要保存的数据的方式。以下属性保存获得与数据库的连接所需的信息。
-
username
:用户为获得访问权限而提供给数据库的名称 -
password
:用户的数据库密码 -
url
:用户要连接到的数据库的 JDBC URL -
datasourceName
:用于检索已向 JNDI 命名服务注册的 DataSource 对象的名称
您必须设置这些属性中的哪一个取决于您将如何构建连接。首选方法是使用DataSource
对象,但是使用 JNDI 命名服务注册DataSource
对象可能不切实际,这通常是由系统 管理 员完成的。因此,所有代码示例均使用DriverManager
机制来获取连接,为此您使用url
属性而不是datasourceName
属性。
下面的代码行设置username
,password
和url
属性,以便可以使用DriverManager
类获得连接。 (在 JDBC 驱动程序的文档中,您将找到 JDBC URL 设置为url
属性的值.)
public void setConnectionProperties(
String username, String password) {
crs.setUsername(username);
crs.setPassword(password);
crs.setUrl("jdbc:mySubprotocol:mySubname");
// ...
您必须设置的另一个属性是command
属性。在参考实现中,数据从ResultSet
对象读入RowSet
对象。产生ResultSet
对象的查询是command
属性的值。例如,以下代码行通过查询设置command
属性,该查询生成一个ResultSet
对象,该对象包含表MERCH_INVENTORY
中的所有数据:
crs.setCommand("select * from MERCH_INVENTORY");
设置关键列
如果要对crs
对象进行任何更新并希望将这些更新保存在数据库中,则必须再设置一条信息:键列。键列本质上与主键相同,因为它们指示一个或多个唯一标识行的列。区别在于,在数据库的表上设置了主键,而在特定的RowSet
对象上设置了键列。以下代码行将crs
的关键列设置为第一列:
int [] keys = {1};
crs.setKeyColumns(keys);
表MERCH_INVENTORY
的第一列是ITEM_ID
。它可以用作键列,因为每个项目标识符都不相同,因此唯一地标识表MERCH_INVENTORY
中的一行,而仅标识一行。另外,此列在MERCH_INVENTORY
表的定义中被指定为主键。方法setKeyColumns
采用一个数组来考虑以下事实:它可能需要两列或更多列来唯一地标识一行。
有趣的是,方法setKeyColumns
并未为属性设置值。在这种情况下,它将设置字段keyCols
的值。键列在内部使用,因此在设置键列之后,您无需再对其进行任何操作。您将在使用 SyncResolver 对象部分中看到如何以及何时使用键列。
填充 CachedRowSet 对象
填充断开连接的RowSet
对象比填充连接的RowSet
对象需要更多的工作。幸运的是,额外的工作是在后台完成的。在完成设置CachedRowSet
对象crs
的准备工作之后,下面的代码行填充crs
:
crs.execute();
crs
中的数据是通过在 command 属性中执行查询而产生的ResultSet
对象中的数据。
不同之处在于,execute
方法的CachedRowSet
实现比JdbcRowSet
实现的功能大得多。或更正确地说,该方法执行的CachedRowSet
对象的读取器委派了其任务,并且执行了更多操作。
每个断开连接的RowSet
对象都有一个SyncProvider
对象,而此SyncProvider
对象就是提供RowSet
对象的读取器(一个RowSetReader
对象)的对象。创建crs
对象时,它用作默认的CachedRowSetImpl
构造函数,该构造函数除了设置属性的默认值外,还将RIOptimisticProvider
实现的实例分配为默认的SyncProvider
对象。
Reader 做什么
当应用程序调用方法execute
时,断开连接的RowSet
对象的读取器在后台进行工作,以向RowSet
对象填充数据。新创建的CachedRowSet
对象未连接到数据源,因此必须获得与该数据源的连接才能从中获取数据。默认SyncProvider
对象(RIOptimisticProvider
)的参考实现提供了一种读取器,该读取器通过使用为用户名,密码以及 JDBC URL 或数据源名称(无论是最近设置的值)设置的值来获取连接。然后,阅读器执行命令查询集。它读取查询产生的ResultSet
对象中的数据,并使用该数据填充CachedRowSet
对象。最后,阅读器关闭连接。
更新 CachedRowSet 对象
在“茶歇”方案中,所有者希望简化操作。所有者决定让仓库中的员工将库存直接 ImportingPDA(个人数字助理)中,从而避免了由第二个人进行数据 Importing 的容易出错的过程。 CachedRowSet
对象在这种情况下是理想的,因为它是轻量级的,可序列化的,并且可以在不连接数据源的情况下进行更新。
所有者将让应用程序开发团队为 PDA 创建一个 GUI 工具,仓库员工将使用该工具来 Importing 库存数据。总部将创建一个CachedRowSet
对象,并在该表中填充当前清单,并将其通过 Internet 发送到 PDA。当仓库员工使用 GUI 工具 Importing 数据时,该工具会将每个条目添加到一个数组中,CachedRowSet
对象将使用该数组在后台执行更新。完成清单后,PDA 将其新数据发送回总部,然后将数据上传到总部。
本节涵盖以下主题:
更新列值
更新CachedRowSet
对象中的数据与更新JdbcRowSet
对象中的数据相同。例如,以下来自CachedRowSetSample.java
的代码片段将ITEM_ID
列的项目标识符为12345
的行中的QUAN
列的值加 1:
while (crs.next()) {
System.out.println(
"Found item " + crs.getInt("ITEM_ID") +
": " + crs.getString("ITEM_NAME"));
if (crs.getInt("ITEM_ID") == 1235) {
int currentQuantity = crs.getInt("QUAN") + 1;
System.out.println("Updating quantity to " +
currentQuantity);
crs.updateInt("QUAN", currentQuantity + 1);
crs.updateRow();
// Synchronizing the row
// back to the DB
crs.acceptChanges(con);
}
插入和删除行
就像更新列值一样,在CachedRowSet
对象中插入和删除行的代码与JdbcRowSet
对象相同。
CachedRowSetSample.java
的以下摘录将新行插入CachedRowSet
对象crs
:
crs.moveToInsertRow();
crs.updateInt("ITEM_ID", newItemId);
crs.updateString("ITEM_NAME", "TableCloth");
crs.updateInt("SUP_ID", 927);
crs.updateInt("QUAN", 14);
Calendar timeStamp;
timeStamp = new GregorianCalendar();
timeStamp.set(2006, 4, 1);
crs.updateTimestamp(
"DATE_VAL",
new Timestamp(timeStamp.getTimeInMillis()));
crs.insertRow();
crs.moveToCurrentRow();
如果总部决定停止库存特定物品,则可能会删除该咖啡本身的行。但是,在这种情况下,使用 PDA 的仓库员工也可以将其删除。以下代码片段查找ITEM_ID
列中的值为12345
的行,并将其从CachedRowSet
crs
删除:
while (crs.next()) {
if (crs.getInt("ITEM_ID") == 12345) {
crs.deleteRow();
break;
}
}
更新数据源
对JdbcRowSet
对象进行更改和对CachedRowSet
对象进行更改之间存在主要区别。因为JdbcRowSet
对象已连接到其数据源,所以方法updateRow
,insertRow
和deleteRow
可以同时更新JdbcRowSet
对象和数据源。但是,在RowSet
对象断开连接的情况下,这些方法将更新CachedRowSet
对象的内存中存储的数据,但不会影响数据源。断开连接的RowSet
对象必须调用方法acceptChanges
才能将其更改保存到数据源。在清单场景中,回到总部,应用程序将调用方法acceptChanges
以使用QUAN
列的新值更新数据库。
crs.acceptChanges();
Writer 做什么
像方法execute
一样,方法acceptChanges
会隐式地执行其工作。方法execute
将其工作委托给RowSet
对象的读取器,而方法acceptChanges
将其任务委托给RowSet
对象的写入器。在后台,编写器打开与数据库的连接,使用对RowSet
对象所做的更改更新数据库,然后关闭连接。
使用默认实现
困难在于可能会发生冲突。冲突是指另一方已更新数据库中与RowSet
对象中更新的值相对应的值的情况。数据库中应保留哪个值?发生冲突时,作者所做的工作取决于冲突的实现方式,并且有很多可能性。在频谱的一端,编写器甚至不检查冲突,而只是将所有更 Rewrite 入数据库。 WebRowSet
对象使用的RIXMLProvider
实现就是这种情况。另一方面,编写者通过设置数据库锁来防止其他人进行更改,从而确保不存在冲突。
crs
对象的 writer 是默认SyncProvider
实现RIOptimisticProvider
提供的对象。 RIOPtimisticProvider
实现的名称源于它使用开放式并发模型的事实。该模型假定几乎没有冲突,因此不设置数据库锁。编写器检查是否存在任何冲突,如果没有冲突,则将对crs
对象所做的更 Rewrite 入数据库,然后这些更改将持久化。如果存在任何冲突,则默认为不将新的RowSet
值写入数据库。
在这种情况下,默认行为效果很好。因为总部的任何人都不可能更改COF_INVENTORY
的QUAN
列中的值,所以不会发生冲突。结果,在仓库中 Importingcrs
对象的值将被写入数据库,因此将是持久的,这是期望的结果。
使用 SyncResolver 对象
但是,在其他情况下,可能存在冲突。为了适应这些情况,RIOPtimisticProvider
实现提供了一个选项,可让您查看冲突中的值并确定应保留的值。此选项是使用SyncResolver
对象。
当编写者完成查找冲突并找到一个或多个冲突时,它将创建一个SyncResolver
对象,该对象包含导致冲突的数据库值。接下来,方法acceptChanges
引发SyncProviderException
对象,应用程序可以catch该对象并使用它来检索SyncResolver
对象。以下代码行检索SyncResolver
对象resolver
:
try {
crs.acceptChanges();
} catch (SyncProviderException spe) {
SyncResolver resolver = spe.getSyncResolver();
}
对象resolver
是一个RowSet
对象,该对象复制crs
对象,只是它只包含数据库中引起冲突的值。所有其他列值均为空。
使用resolver
对象,您可以遍历其行以找到不为 null 的值,因此是引起冲突的值。然后,您可以将值定位在crs
对象中的相同位置并进行比较。以下代码片段检索resolver
并使用SyncResolver
方法nextConflict
遍历具有冲突值的行。对象resolver
获取每个冲突值的状态,如果它是UPDATE_ROW_CONFLICT
,则表示发生冲突时crs
正在try更新,因此resolver
对象获取该值的行号。然后,代码将crs
对象的光标移动到同一行。接下来,代码在resolver
对象的该行中找到包含冲突值的列,该值将为非 null 值。从resolver
和crs
对象中检索该列中的值后,可以比较两者并确定要持久化的对象。最后,代码使用setResolvedValue
方法在crs
对象和数据库中设置该值,如以下代码所示:
try {
crs.acceptChanges();
} catch (SyncProviderException spe) {
SyncResolver resolver = spe.getSyncResolver();
// value in crs
Object crsValue;
// value in the SyncResolver object
Object resolverValue;
// value to be persistent
Object resolvedValue;
while (resolver.nextConflict()) {
if (resolver.getStatus() ==
SyncResolver.UPDATE_ROW_CONFLICT) {
int row = resolver.getRow();
crs.absolute(row);
int colCount =
crs.getMetaData().getColumnCount();
for (int j = 1; j <= colCount; j++) {
if (resolver.getConflictValue(j)
!= null) {
crsValue = crs.getObject(j);
resolverValue =
resolver.getConflictValue(j);
// ...
// compare crsValue and
// resolverValue to
// determine the value to be
// persistent
resolvedValue = crsValue;
resolver.setResolvedValue(
j, resolvedValue);
}
}
}
}
}
Notifying Listeners
成为 JavaBeans 组件意味着RowSet
对象可以在发生某些事情时通知其他组件。例如,如果RowSet
对象中的数据发生更改,则RowSet
对象可以将该更改通知相关方。关于此通知机制的好处是,作为应用程序程序员,您要做的就是添加或删除将要通知的组件。
本节涵盖以下主题:
设置监听器
RowSet
对象的侦听器是一个通过RowSetListener
interface实现以下方法的组件:
-
cursorMoved
:定义RowSet
对象中的光标移动时侦听器将执行的操作(如果有)。 -
rowChanged
:定义当行中的一个或多个列值已更改,已插入行或已删除行时,侦听器将执行的操作(如果有)。 -
rowSetChanged
:定义在RowSet
对象已填充新数据时,侦听器将执行的操作(如果有)。
可能想成为侦听器的组件的一个示例是BarGraph
对象,该对象以图形表示RowSet
对象中的数据。随着数据的更改,BarGraph
对象可以更新自身以反映新数据。
作为应用程序程序员,要利用通知机制,您唯一要做的就是添加或删除侦听器。以下代码行意味着,每次crs
对象的光标移动,crs
中的值发生更改或crs
整体获取新数据时,BarGraph
对象bar
都会收到通知:
crs.addRowSetListener(bar);
您还可以通过删除侦听器来停止通知,如下面的代码行所示:
crs.removeRowSetListener(bar);
使用 Coffee Break 场景,假设总部定期与数据库核对,以获取其在线销售的咖啡的最新价格表。在这种情况下,侦听器是 Coffee Break 网站上的PriceList
对象priceList
,该对象必须实现RowSetListener
方法cursorMoved
,rowChanged
和rowSetChanged
。 cursorMoved
方法的实现可能什么也不做,因为游标的位置不会影响priceList
对象。另一方面,rowChanged
和rowSetChanged
方法的实现必须确定已进行了哪些更改并相应地更新priceList
。
通知的工作方式
在参考实现中,导致任何RowSet
事件的方法都会自动通知所有已注册的侦听器。例如,任何移动光标的方法也会在每个侦听器上调用方法cursorMoved
。同样,方法execute
在所有侦听器上调用方法rowSetChanged
,并且acceptChanges
在所有侦听器上调用rowChanged
。
发送大量数据
示例代码CachedRowSetSample.testCachedRowSet演示了如何以较小的片段发送数据。