现在的位置: 首页 > 综合 > 正文

Java 6 RowSet 使用完全剖析

2014年01月13日 ⁄ 综合 ⁄ 共 5319字 ⁄ 字号 评论关闭

Java 6 RowSet 使用完全剖析

来源:IBM http://www.ibm.com/developerworks/cn/java/j-lo-java6rowset/

C# 提供了 DataSet,可以将数据源中的数据读取到内存中,进行离线操作,然后再同步到数据源。同样,在 Java 中也提供了类似的实现,即 RowSetjavax.sql.rowset 包下,定义了五个不同的 RowSet 接口,供不同的场合使用。本文将分别对这五个 RowSet 的使用场合以及详尽用法进行介绍,并且描述使用中可能出现的问题,以提醒读者在实际使用时绕开这些问题。

1.1    RowSet 简介

javax.sql.rowset JDK 1.4 引入,从 JDK 5.0 开始提供了参考实现。它主要包括 CachedRowSetWebRowSetFilteredRowSetJoinRowSet JdbcRowSet。 除了 JdbcRowSet 依然保持着与数据源的连接之外,其余四个都是 Disconnected RowSet

相比较 java.sql.ResultSet 而言,RowSet 的离线操作能够有效的利用计算机越来越充足的内存,减轻数据库服务器的负担,由于数据操作都是在内存中进行然后批量提交到数据源,灵活性和性能都有了很大 的提高。RowSet 默认是一个可滚动,可更新,可序列化的结果集,而且它作为 JavaBeans,可以方便地在网络间传输,用于两端的数据同步。

1.2    类继承结构

RowSet 继承自 ResultSet,其他五个 RowSet 接口均继承自 RowSet。下图是它们的继承关系。


1. 继承结构图
图 1. 继承结构图

1. RowSet 接口说明

CachedRowSet

最 常用的一种 RowSet。其他三种 RowSetWebRowSetFilteredRowSetJoinRowSet)都是直接或间接继承于它并进行了扩展。它提供了对数据库的离线 操作,可以将数据读取到内存中进行增删改查,再同步到数据源。可串行化,可作为 JavaBeans 在网络间传输。支持事件监听,分页等特性。

WebRowSet

继承自 CachedRowSet,并可以将 WebRowSet 写到 XML 文件中,也可以用符合规范的 XML 文件来填充 WebRowSet

FilteredRowSet

通过设置 Predicate(在 javax.sql.rowset 包中),提供数据过滤的功能。可以根据不同的条件对 RowSet 中的数据进行筛选和过滤。

JoinRowSet

提供类似 SQL JOIN 的功能,将不同的 RowSet 中的数据组合起来。目前在 Java 6 中只支持内联(Inner Join)。

JdbcRowSet

ResultSet 的一个封装,使其能够作为 JavaBeans 被使用,是唯一一个保持数据库连接的 RowSet

1.3    实验环境

本文示例的实验环境如下:

  • Java 环境:Sun JDK 6.0

  • 数据库:derby-10.3.1.4

  • 数据库名:TESTDB

  • 数据库用户名及密码:均使用 derby 默认用户名和密码。

  • 表及测试数据:创建两个表:CUSTOMERS ORDERS,并分别插入测试数据。

  • 示例代码以附件形式提供 下载


2. CUSTOMERS

ID

NAME

REMARK

1

Tom

Tom is VIP

2

Jim

null


3. ORDERS

ID

USER_ID

PRODUCT

1

1

Book

2

1

Computer

3

2

Phone

1.4    使用 CachedRowSet

1.4.1   填充 CachedRowSet 的两种方式

CachedRowSet 提供了两个用来获取数据的方法,一个是 execute(),另一个是 populate(ResultSet)

使用 execute() 填充 CachedRowSet 时,需要设置数据库连接参数和查询命令 command,如下示例代码:


清单 1. 使用 execute()

               

cachedRS.setUrl(DBCreator.DERBY_URL);

cachedRS.setCommand(DBCreator.SQL_SELECT_CUSTOMERS);

// derby 默认用户名和密码都是 "APP",也可以不设置。

cachedRS.setUsername("APP"); //$NON-NLS-1$

cachedRS.setPassword("APP"); //$NON-NLS-1$

cachedRS.execute();

 

cachedRS 根据设置的 urlusernamepassword 三个参数去创建一个数据库连接,然后执行查询命令 command,用结果集填充 cachedRS,最后关闭数据库连接。execute() 还可以直接接受一个已经打开的数据库连接,假设 conn 为一个已经打开的数据库连接,下段示例代码与上段代码结果一致:


清单 2. 使用 execute(Connection)

               

cachedRS.execute(conn);

cachedRS.setCommand(DBCreator.SQL_SELECT_CUSTOMERS);

cachedRS.execute();

 

填充 CachedRowSet 的第二个方法是使用 populate(ResultSet)


清单 3. 使用 populate(ResultSet)

               

ResultSet rs = stmt.executeQuery(DBCreator.SQL_SELECT_CUSTOMERS);

cachedRS.populate(rs);

rs.close();

 

CachedRowSet 本身也是继承于 ResultSet,因此,也可以用一个有数据的 CachedRowSet 来填充另一个 CachedRowSet

1.4.2   更新、删除、插入数据

更新数据。先把游标(cursor)移到要更新的行,根据每列的类型调用对应的 updateXXX(index, updateValue),再调用 updateRow() 方法。此时,只是在内存中更新了该行,同步到数据库需要调用方法 acceptChanges() acceptChanges(Connection)。如果 CachedRowSet 中保存着原数据库连接信息,则可以调用 acceptChanges();否则,则应该传入可用的数据库连接或重新设置数据库连接参数。下段示例代码更新第一行的第二列。


清单 4. 更新

               

cachedRS.first();

cachedRS.updateString(2, "Hello"); //$NON-NLS-1$

cachedRS.updateRow();

cachedRS.acceptChanges();

 

删除数据。把游标移到要删除的行,调用 deleteRow(),再同步回数据库即可。


清单 5. 删除

               

cachedRS.last();

cachedRS.deleteRow();

cachedRS.acceptChanges();

 

在删除数据时,需要注意布尔值 showDeleted 这个属性的使用。CachedRowSet 提供了 getShowDeleted() setShowDeleted(boolean value) 两个方法来读取和设置这个属性。showDeleted 是用来判断被标记为删除且尚未同步到数据库的行在 CachedRowSet 中是否可见。true 为可见,false 为不可见。默认值为 false

插入数据。插入操作稍微比更新和删除复杂。先看下段示例代码。


清单 6. 新增

               

cachedRS.last();

cachedRS.moveToInsertRow();

cachedRS.updateInt(1, 3);

cachedRS.updateString(2, "Bob"); //$NON-NLS-1$

cachedRS.updateString(3, "A new user"); //$NON-NLS-1$

cachedRS.insertRow();

cachedRS.moveToCurrentRow();

cachedRS.acceptChanges();

 

新插入的行位于当前游标的下一行。本例中,先把游标移到最后一行,那么在新插入数据后,新插入的行就是最后一行了。在新插入行时,一定要先调用方法 moveToInsertRow(),然后调用 updateXXX() 设置各列值,再调用 insertRow(),最后再把游标移到当前行。注意一定要遵循这个步骤,否则将抛出异常。

1.4.3    冲突处理

当我们使用 CachedRowSet 更新数据库时,有可能因为内存中的数据过期而产生冲突。此时更新数据库的方法 acceptChanges() 会抛出 SyncProviderException,由此我们可以捕获产生冲突的原因并手动进行解决。


清单 7. 冲突

               

ResultSet rs = stmt.executeQuery(DBCreator.SQL_SELECT_CUSTOMERS);

CachedRowSet cachedRS = new CachedRowSetImpl();

cachedRS.populate(rs);

cachedRS.setUrl(DBCreator.DERBY_URL);

 

// 修改数据库中的数据

stmt.executeUpdate("UPDATE CUSTOMERS SET NAME = 'Terry' WHERE ID = 1");

 

// CachedRowSet 中更新同一行

cachedRS.absolute(1);

cachedRS.updateString(3, "Tom is not VIP");

cachedRS.updateRow();

 

SyncResolver resolver = null;

try {

    cachedRS.acceptChanges();

} catch (SyncProviderException e) {

    resolver = e.getSyncResolver();

}

 

while (resolver.nextConflict()) {

    System.out.println(resolver.getStatus());

}

 

我们首先填充 cachedRS,然后在数据库中直接修改 ID 1 的行,将 NAME 字段设为 "Terry",同时用 cachedRS 修改 ID 1 的行的 REMARK 字段,最后使用 acceptChanges 跟数据库进行同步。此时 cachedRS 中记录的原始值与数据库中的值不一致,从而产生冲突,抛出 SyncProviderException,数据也会更新失败。接下来我们通过 SyncProviderException 得到 SyncResolver 实例并遍历了产生的所有冲突。

SyncResolver 继承了 RowSet 接口,我们可以像使用一般 RowSet 一样操作它。SyncResolver 的实例拥有与正在同步的 RowSet 相同的行数和列数。使用 nextConflict() previousConflict() 可以遍历所有产生的冲突,getStatus() 可以获得冲突的类型。在 SyncResolve 中定义了四种类型,分别 是:DELETE_ROW_CONFLICTINSERT_ROW_CONFLICTNO_ROW_CONFLICTUPDATE_ROW_CONFLICT。 上例中产生的是 UPDATE_ROW_CONFLICT

注:目前 Sun JDK SyncResolver 的支持非常有限,只实现了 SyncResolver 接口中定义的方法,调用从 RowSet 接口继承的方法都会抛出 UnsupportedOperationException 异常;getConflictValue() 返回都是 null

1.4.4    事件监听

一 个监听器需要实现 RowSetListener 接口。RowSetListener 支持三种事件监听:cursor movedrow changed rowSet changed。假定 Listener 实现了 RowSetListener 接口,看示例代码。


清单 8. 注册事件监听器

               

Listener listener = new Listener();

cachedRS.addRowSetListener(listener);

updateOnRowSet(cachedRS);

cachedRS.removeRowSetListener(listener);

 

updateOnRowSet() 所做的操作就是将游标移到第一行,更新,再同步回数据库。在这个方法中,依次触发了 Listener 的三个事件。下表列出了 CachedRowSet 中会触发监听器的所有方法。


4. CachedRowSet 中会触发监听器的方法

 

cursor moved

row changed

rowSet changed

absolute()

 

 

relative()

 

 

抱歉!评论已关闭.