`
alanland
  • 浏览: 641275 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Java 6 RowSet 使用完全剖析

    博客分类:
  • Java
阅读更多

Java 6 RowSet 使用完全剖析



级别: 中级

徐 睿智     刘 威      许 彬 

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

2008 年 6 月 05 日

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

RowSet 简介

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

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





回页首


类继承结构

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


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

表 1. RowSet 接口说明
CachedRowSet 最常用的一种 RowSet。其他三种 RowSet(WebRowSet,FilteredRowSet,JoinRowSet)都是直接或间接继承于它并进行了扩展。它提供了对数据库的离线操作,可以将数据读取到内存中进行增删改查,再同步到数据源。可串行化,可作为 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。




回页首


实验环境

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

  • 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




回页首


使用 CachedRowSet

填充 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 根据设置的 url、username、password 三个参数去创建一个数据库连接,然后执行查询命令 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。

更新、删除、插入数据

更新数据。先把游标(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(),最后再把游标移到当前行。注意一定要遵循这个步骤,否则将抛出异常。

冲突处理

当我们使用 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_CONFLICT,INSERT_ROW_CONFLICT,NO_ROW_CONFLICT,UPDATE_ROW_CONFLICT。上例中产生的是 UPDATE_ROW_CONFLICT。

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

事件监听

一个监听器需要实现 RowSetListener 接口。RowSetListener 支持三种事件监听:cursor moved、row 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()    
next()    
previous()    
first()    
last()    
beforeFirst()    
afterLast()    
updateRow()    
deleteRow()    
insertRow()    
undoDelete()    
undoUpdate()    
undoInsert()    
populate()    
acceptChanges()    
acceptChanges(Connection)    
execute()    
execute(Connection)    
nextPage()    
previousPage()    
restoreOriginal()    
release()    

事务

事务对于保证数据的一致性是非常重要的。CachedRowSet 专门提供了处理事务的接口,从而可以保证同步数据的原子性和一致性。CachedRowSet 默认是不使用事务的。


清单 9. 事务代码一
                
cachedRS.absolute(1);
// 第一列不能为 null,更新时将产生冲突
cachedRS.updateNull(1);
cachedRS.updateRow();

cachedRS.next();
cachedRS.updateString(2, "Terry");
cachedRS.updateRow();
try {
    cachedRS.acceptChanges(conn);
} catch (SyncProviderException e) {
    // expected
}

rs = stmt.executeQuery(DBCreator.SQL_SELECT_CUSTOMERS);
cachedRS = new CachedRowSetImpl();
cachedRS.populate(rs);
printRowSet(cachedRS);

我们更新了 cachedRS 的第一行和第二行,并将第一行第一列错误的设置为 null。由于 CachedRowSet 默认不使用事务,第一行没有更新,而第二行更新成功。我们也可以手动控制事务的作用范围。


清单 10. 事务代码二
                
cachedRS.absolute(1);
cachedRS.updateNull(1);
cachedRS.updateRow();

cachedRS.next();
cachedRS.updateString(2, "Terry");
cachedRS.updateRow();
conn.setAutoCommit(false);
try {
    cachedRS.acceptChanges(conn);
    cachedRS.commit();
} catch (SyncProviderException e) {
    // expected
    cachedRS.rollback(); 
}
conn.setAutoCommit(true);
rs = stmt.executeQuery(DBCreator.SQL_SELECT_CUSTOMERS);
cachedRS = new CachedRowSetImpl();
cachedRS.populate(rs);
printCachedRowSet(cachedRS);

与前面的例子不同的是,这里我们关闭了自动提交模式,并且在同步失败后回滚了事务,避免了数据不一致的情况。

需要注意的是,如果需要手动控制事务的范围,在调用 execute 或 acceptChanges 时必须传入 Connection,否则再调用 CachedRowSet 的 commit() 或 rollback() 会抛 NullPointerException。实际上 CachedRowSet 依然是通过在内部保存 Connection 的引用来实现事务操作的。

分页

由于 CachedRowSet 是将数据临时存储在内存中,因此对于许多 SQL 查询,会返回大量的数据。如果将整个结果集全部存储在内存中会占用大量的内存,有时甚至是不可行的。对此 CachedRowSet 提供了分批从 ResultSet 中获取数据的方式,这就是分页。应用程序可以简单的通过 setPageSize 设置一页中数据的最大行数。也就是说,如果页大小设置为 5,一次只会从数据源获取 5 条数据。下面的代码示范了如何进行简单分页操作。(分页部分代码默认 ORDERS 表中有 10 条数据)


清单 11. 分页代码一
                
ResultSet rs = stmt.executeQuery(DBCreator.SQL_SELECT_ORDERS);
CachedRowSet cachedRS = new CachedRowSetImpl();
// 设置页大小
cachedRS.setPageSize(4);
cachedRS.populate(rs, 1);

while (cachedRS.nextPage()) {
printRowSet(cachedRS);
}
        
while (cachedRS.previousPage()) {
   printRowSet(cachedRS);
}

可以看到只需要在 populate 之前使用 setPageSize 设置页的大小,就可以轻松实现分页了。每次调用 nextPage 或 previousPage 进行翻页后,行游标都会被自动移动到当前页第一行的前面,并且只能在当前页内移动。这样我们对每一页都可以像新的数据集一样进行遍历,非常方便。这里需要注意的是:

  1. 用来填充 CachedRowSet 的 ResultSet 必须是可滚动的(Scrollable)。
  2. populate 必须使用有两个参数的版本,否则无法进行分页。读者可以将 cachedRS.populate(rs, 1); 换成 cachedRS.populate(rs); 看看会有怎样的情况发生。
  3. ResultSet rs 必须在遍历完毕后才能关闭,否则翻页遍历时会抛 SQLException

我们注意到在使用分页遍历数据集时,nextPage() 是最先被调用的,也就是说页与行相似,最开始的页游标是指向第 0 页

分享到:
评论

相关推荐

    java文集

    Java 6 RowSet 使用完全剖析 结合Spring2.0和ActiveMQ进行异步消息调用 struts+hibernate增删改查(一) AXIS 布署问题 struts+hibernate增删改查(二) MySQL中如何实现Top N及M至N段的记录查询?...

    Java JDK 7学习笔记(国内第一本Java 7,前期版本累计销量5万册)

    必须要时从Java SE API的源代码分析,了解各种语法在Java SE API中如何应用。  《Java JDK 7学习笔记》将IDE操作纳为教学内容之一,使读者能与实践结合,提供的视频教学能更清楚地帮助读者掌握操作步骤。 内容简介 ...

    Java语言基础下载

    第二十二章:使用Java解析XML 397 学习目标 397 解析器的介绍 398 DOM以及广义的基于树的处理具有几个优点 399 文档对象模型(DOM)解析实例 402 DOM对象 404 DOM解析的例子: 406 SAX解析实例 409 DOM4J解析实例 ...

    Java数据编程指南

    使用JSP进行数据访问 访问数据 范例应用程序 深入性主题 小结 第17章 分析和生成XML 文档和数据 XML概述 Java XML技术 生成XML 读取XML 小结 第18章 WAP客户机 WAP概览 ...

    java学习路线图 java学习路线图

    - **内存分析**:学习Java内存模型,理解栈、堆以及垃圾回收机制。 - **递归**:学习如何编写和理解递归函数。 - **集合类**:熟悉ArrayList、LinkedList、HashSet、HashMap等数据结构的使用。 - **泛型**:学习...

    Excel与Java的传输工具poi-3.17

    12. **接口与实现**:如RowSet接口,它的实现类如Java POI的 JRSSerializableRowSet 和 JdbcRowSetImpl,可将Excel数据作为JDBC结果集处理。 使用Apache POI时,开发人员需要了解这些基本概念,并根据实际需求选择...

    java自学路线图java自学路线图.doc

    深入理解内存分析、递归以及集合类(如ArrayList、LinkedList和HashMap)的使用。泛型提供类型安全,自动打包与解包简化了数据类型的转换。Annotation用于元数据,IO流处理文件操作,多线程和线程同步(如...

    Java反序列化实战.pdf

    - **PoC示例**:通过构造特定的JSON数据格式(如使用`com.sun.rowset.JdbcRowSetImpl`类)可以触发远程代码执行漏洞。 - **修复措施**:添加黑名单机制,禁止加载敏感类。 - **Weblogic安全漏洞**: - **CVE-...

    JDBC培训资料java连接数据库

    JDBC(Java Database Connectivity)是Java编程语言中用于与各种数据库进行交互的一种API。它提供了一种标准的接口,使得Java开发者能够通过编写Java代码来访问数据库,执行SQL语句,处理查询结果等。达内的JDBC培训...

    java自学之路num1

    深入理解内存分析,了解递归操作,掌握集合类(如ArrayList、LinkedList、HashMap等),泛型的使用,自动装箱拆箱原理,以及Annotation(注解)的使用。此外,还需要学习多线程编程,包括线程的创建、同步...

    java学习之路

    在Java中,通过`java.util.regex`包来实现正则表达式的使用。 - 常见的正则表达式操作包括`Pattern`和`Matcher`类,用于编译和匹配正则表达式。 #### 反射机制 - Java的反射机制允许运行时获取类的信息并创建对象。...

    jdk1.4,jdk1.5,jdk6

    本文将深入探讨JDK 1.4、JDK 1.5(也称为Java 5)和JDK 6这三个重要版本的关键特性。 **JDK 1.4** JDK 1.4是Java发展历程中的一个里程碑,发布于2002年。这个版本引入了许多关键的新特性,包括: 1. **异常链**:...

    java知识点汇总学习路线与笔记

    - **内存分析**:Java程序运行时会涉及到多种类型的内存区域,比如堆内存、栈内存、方法区等。理解这些区域的功能和作用对于优化程序性能至关重要。 - **递归**:递归是一种算法或程序设计技巧,在函数或过程中调用...

    marshalsec.pdf

    - 在可能的情况下,限制或完全避免使用反序列化功能,特别是对于不受信任的数据。 - 使用更安全的数据交换格式替代Java对象序列化。 - 采用自动化工具和库来帮助识别和修复潜在的安全漏洞。 整体而言,本文档深入...

    oracle数据库连接包

    - ojdbc是Oracle提供的JDBC驱动,例如ojdbc8.jar,它是Type 4驱动,完全用Java编写,无需安装Oracle客户端。 - 配置数据源:在Java应用中,可以配置DataSource对象,提供更高级的连接池管理,提高性能和资源利用率...

    数据库数据导出excel表格

    对于大数据量,可以考虑使用迭代器模式(`RowSet`)减少内存消耗,或者分批写入数据到Excel,避免一次性加载所有数据。 总结,通过以上步骤,我们可以使用Java和Apache POI实现从数据库导出数据到Excel的功能。这...

Global site tag (gtag.js) - Google Analytics