目录 | 上一页 | 下一页 JDBCTM 指南:入门


5 结果集增强功能

本章讨论 JDBC 结果集的新增功能。这些新增功能的目标是为结果集增加两个新的基本能力:可滚动性和可更新性。同时,我们还新增了几种方法,它们可以优化 JDBC 驱动程序在处理结果时的性能。本文档中还使用了各种实例来说明这些新功能。

5.1 可滚动性

通过执行语句而创建的结果集不仅支持向前(从第一个到最后一个)浏览内容,而且还支持向后(从最后一个到第一个)浏览内容的能力。支持这种能力的结果集被称为可滚动的结果集。可滚动的结果集同时也支持相对定位和绝对定位。绝对定位指的是通过指定在结果集中的绝对位置而直接移动到某行的能力,而相对定位则指的是通过指定相对于当前行的位置来移动到某行的能力。JDBC 2.0 中对绝对定位和相对定位的定义在 X/Open SQL CLI 规范中被作为样板。

5.2 结果集类型

JDBC 1.0 API 只提供一种结果集类型,即只向前类型。JDBC 2.0 API 提供了三种结果集类型:只向前型、滚动不敏感型及滚动敏感型。正如其名称所暗示的,新增的结果集类型支持可滚动性。但是在结果集打开时使变化可见的能力却有所区别。

滚动不敏感的结果集通常对在结果集打开时所作的变化不敏感。滚动不敏感的结果集提供它所含基本数据的静态视图。在创建滚动不敏感的结果集时,结果集中各行的成员顺序、列值通常都是固定的。

相反,滚动敏感的结果集对在结果集打开时所作的变化就很敏感。它提供的是基本数据的“动态”视图。例如,在使用滚动敏感的结果集时,行的基本列值是可见的。结果集中各行的成员和顺序可以是固定的—这一点由实现来定义。

5.3 并发类型

应用程序可以为结果集选择两种不同的并发类型:只读的和可更新的。

采用只读并发类型的结果集不允许对其内容进行更新。因为一个数据项上可以同时加有任意数目的只读锁,所以这可以提高事务处理之间的整体并发等级。

可更新的结果集允许更新,而且可以使用数据库写入锁来调解不同事务处理对相同数据项的访问。因为同时只允许数据项持有一个写入锁,所以这样会降低并发性。另一种做法是,如果认为对数据的访问冲突发生几率很小,则可以采用优化并发控制机制。优化并发控制的实现通常通过比较行的数值或版本号来确定是否发生了更新冲突。

5.4 性能

可以为 JDBC 2.0 驱动程序提供两种性能提示,以提高对结果集的访问效率。特别地,当需要多个行时,可以指定从数据库取出的行数,而且还可以给出处理各行的方向 — 向前、向后或未知。在任何时候都可以为个别结果集更改这些值。JDBC 驱动程序如果愿意可以忽略性能提示。

5.5 创建结果集

下例说明了结果集的创建过程,其中的结果集是只向前的且采用了只读并发。本例没有给出性能提示,所以驱动程序可以它所认为的能带来最佳性能的方式。由于没有指定连接的事务处理隔离层,因此所创建的结果集采用基本数据库的缺省事务处理隔离层。请注意:本例中的代码只是 JDBC 1.0 代码,因此它所生成的结果集类型与 JDBC 1.0 所生成的类型一样。

Connection con = DriverManager.getConnection(
	"jdbc:my_subprotocol:my_subname");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
	"SELECT emp_no, salary FROM employees");

下例创建可滚动结果集,该结果集是可更新的且对于更新敏感。它请求一次从数据库取出 25 个数据行。

Connection con = DriverManager.getConnection(
	"jdbc:my_subprotocol:my_subname");

Statement stmt = con.createStatement(
	ResultSet.TYPE_SCROLL_SENSITIVE,
	ResultSet.CONCUR_UPDATABLE);
stmt.setFetchSize(25);

ResultSet rs = stmt.executeQuery(
	"SELECT emp_no, salary FROM employees");


下例创建的结果集与前例中的结果集具有同样的属性,但它采用预先准备好的语句来生成结果集。

PreparedStatement pstmt = con.prepareStatement(
	"SELECT emp_no, salary FROM employees where emp_no = ?",
	ResultSet.TYPE_SCROLL_SENSITIVE,
	ResultSet.CONCUR_UPDATABLE);

pstmt.setFetchSize(25);
pstmt.setString(1, "100010");
ResultSet rs = pstmt.executeQuery();


可以调用方法 DatabaseMetaData.supportsResultSetType() 来查看 JDBC 驱动程序支持何种结果集类型。然而,应用程序可能仍要求 JDBC 驱动程序创建使用该驱动程序所不支持的结果集类型的 StatementPreparedStatementCallableStatement 对象。这种情况下,驱动程序应该在生成该语句的 Connection 上发出 SQLWarning,并且依据以下原则为该语句的结果集类型选择另外一个替代值:

某些情况下,JDBC 驱动程序可能需要在语句执行时为 ResultSet 选择另外一个结果集类型或并发类型。例如,包含多表有连接的 SELECT 语句不能生成可更新的 ResultSet。这种情况下,JDBC 驱动程序应该在生成 ResultSetStatementPreparedStatementCallableStatement 上发出 SQLWarning,并且如上所述选择合适的结果集类型或并发类型。应用程序通过分别调用 ResultSet.getType()getConcurrency() 方法可以确定 ResultSet 的实际结果集类型和并发类型。

5.6 更新

如果结果集的并发类型是 CONCUR_UPDATABLE,则该结果集是可更新的。用户可以更新、插入和删除可更新的结果集中的行。下例更新了结果集的第一行。它使用 ResultSet.updateXXX() 方法来修改当前行中某列的数值,但是并不更新基本数据库。调用 ResultSet.updateRow() 方法时将对数据库进行更新。可以用名称和编号来指定列。

rs.first();
rs.updateString(1, "100020");
rs.updateFloat("salary", 10000.0f);
rs.updateRow();

如果应用程序在调用 updateRow() 之前将光标从当前行移开,则 JDBC 驱动程序必须丢弃应用程序所作的更新。此外,应用程序可以调用 ResultSet.cancelRowUpdates() 方法显式地取消对某行所作的更新。必须在调用 updateXXX() 之后及在调用 updateRow() 之前调用 cancelRowUpdates() 方法,否则调用无效。

下例说明了如何删除行。本例从数据库中删除了结果集中的第五行。

rs.absolute(5);
rs.deleteRow();

下例显示了如何将新行插入到结果集中。JDBC 2.0 API 定义了插入行的概念。这个概念与每个结果集相关联,并且在新行插入到结果集中之前用它作为创建新行内容的实施场所。本例使用 ResultSet.moveToInsertRow() 方法来将结果集的光标定位到插入行上。使用 ResultSet.updateXXX()ResultSet.getXXX() 方法来从插入行中更新和检索单个列值。在调用 ResultSet.moveToInsertRow() 之后立即取消对插入行内容的定义。换句话说,调用 ResultSet.getXXX() 方法所返回的数值在调用 moveToInsertRow() 之后是未定义的,直到调用 ResultSet.updateXXX() 设置该数值为止。

在插入行上调用 ResultSet.updateXXX() 并不会更新基本数据库或结果集。当插入行中所有的列值均被设置之后,就应调用 ResultSet.insertRow() 来同时更新结果集和数据库。如果在插入行上调用 updateXXX() 时并没有为某列给定数值,或者结果集中遗漏了某列,则该列必须允许空值。否则,调用 insertRow() 就会抛出 SQLException

rs.moveToInsertRow();
rs.updateString(1, "100050");
rs.updateFloat(2, 1000000.0f);
rs.insertRow();
rs.first();


当光标暂时定位在插入行上时,结果集将记住“在结果集中的”当前光标位置。要离开插入行,可以调用任何一种光标定位方法,包括特殊方法 ResultSet.moveToCurrentRow() ,该方法使游标重新指向调用 ResultSet.moveToInsertRow() 之前的当前行。上例中,我们调用了 ResultSet.first() 来离开插入行并且移到结果集的第一行。

由于数据库实现之间的差异,JDBC 2.0 API 没有确切指明哪些 SQL 查询一定会为支持可更新性的 JDBC 驱动程序生成可更新的结果集。然而,开发人员通常可以期望满足以下准则的查询来生成可更新的结果集:

5.7 光标移动实例

结果集维护称为光标的内部指针,光标指示结果集中的当前所访问的行。结果集光标类似于计算机屏幕上的光标,后者指示当前屏幕位置。只向前的结果集所支持的光标只能向前移动结果集的内容。因此,将从第一行开始顺序访问各行。

通过调用 ResultSet.next() 方法可以向前遍历结果集,这与 JDBC 1.0 API 中一样。另外,可滚动的结果集 — 其类型不是只向前类型的任何结果集 — 实现了方法 beforeFirst()。调用该方法可以将光标定位到结果集中第一行之前。

下例将光标定位到第一行之前,然后向前遍历结果集的内容。本例使用 getXXX() 方法(属于 JDBC 1.0 API 方法)来检索列值。

rs.beforeFirst();
while ( rs.next()) {
	System.out.println(rs.getString("emp_no") +
			   " " + rs.getFloat("salary"));
}

当然,也可以向后遍历可滚动的结果集,如下所示。

rs.afterLast();
while (rs.previous()) {
	System.out.println(rs.getString("emp_no") +
		" " + rs.getFloat("salary"));
}

在本例中, ResultSet.afterLast() 方法将可滚动结果集的光标定位到结果集中最后一行的后面。本例调用 ResultSet.previous() 方法将光标移动到最后一行,然后移动到倒数第二行,依此类推。当再没有其它行时,ResultSet.previous() 返回 false。这样,在访问了所有行之后循环即终止。

在分析了 ResultSet 接口之后,读者无疑将认识到有多种方式可以遍历可滚动结果集的各行。然而,小心一些还是值得的,如下例所示。下例显示了一种不正确的方式。

// 不正确!!!
while (!rs.isAfterLast()) {
	rs.relative(1);
	System.out.println(rs.getString("emp_no") +
		 " " + rs.getFloat("salary"));
}


本例试图向前遍历可滚动结果集。之所以不正确的原因有几个:一个错误是如果在结果集为空时调用 ResultSet.isAfterLast()。因为没有最后一行,所以它将返回假值,并且执行循环体, 而这不是所需结果。当光标定位在包含有数据的结果集的第一行之前时,还会出现另外的问题。这种情况下,由于没有当前行,调用 rs.relative(1) 就是错误的。

以下的代码示例解决了上例中的问题。本例中通过调用 ResultSet.first() 来区别空结果集和包含数据的结果集这两种情况。由于仅在结果集非空时才调用 ResultSet.isAfterLast(),所以循环控制能正确地工作。同时,由于 ResultSet.first() 最初将光标定位到第一行,所以 ResultSet.relative(1) 将步进地经过结果集的各行。

if (rs.first()) {
	while (!rs.isAfterLast()) {
		System.out.println(rs.getString("emp_no") +
			" " + rs.getFloat("salary"));
	    	rs.relative(1);
	}
}

5.8 检测和查看变化

迄今为止,我们已经介绍了不同的结果集类型且列出了一些有关如何创建、更新及遍历特定类型结果集的实例。本节将详细描述结果集类型之间的差异,同时介绍这些差异对使用结果集的应用程序的意义。

JDBC 2.0 API 提供了三种不同的结果集类型:只向前类型、滚动不敏感类型和滚动敏感类型。这三种类型在使基本数据的变化对应用程序可见的能力上差异很大。结果集的这方面功能对于支持可滚动性的结果集类型特别有用,因为这些结果集类型允许在结果集打开时多次访问特定行。

5.8.1 变化的可见性

我们以描述在事务处理层变化的可见性来开始讨论本主题。首先,请注意表面上很明显的事实,即事务处理所作的所有更新对其自身都是可见的。然而,其它事务处理所作的变化(更新、插入和删除)对某个事务处理是否可见则由事务处理隔离层来确定。要设置事务处理的隔离层,可调用

con.setTransactionIsolation(TRANSACTION_READ_COMMITTED);


其中的变量 con 具有类型 Connection。如果系统中的所有事务处理在 TRANSACTION_READ_COMMITTED 隔离层或更高隔离层上执行,则事务处理只能看到其它事务处理已提交的变化。在结果集打开时对包含结果集的事务处理可见的变化总是通过结果集可见。事实上,这就是一个事务处理所作更新对另外一个事务处理可见的含义。

但是,当结果集打开时所作变化又如何呢?能否用某种方式(例如调用 ResultSet.getXXX())使这些变化通过结果集中可见? 当结果集处于打开状态时,特定结果集是否会暴露由其它事务处理或属于同一个事务处理的其它结果集对其基本数据所作的变化(我们将这两种变化总称为“它方的变化”)以及由其自身对基本数据所作的变化将取决于结果集类型。

5.8.2 它方的变化

打开滚动不敏感的结果集后,滚动不敏感结果集就不会让它方(其它事务处理和同一个事务处理中的其它结果集)所作的任何变化可见。滚动不敏感结果集的内容相对于它方所作变化是静态的 — 成员、顺序和行值都是固定的。例如,如果另外一个事务处理(在结果集打开时)删除了包含在静态结果集中的某行,则该行仍将保持可见。实现滚动不敏感结果集的一种方法是创建结果集数据的私有副本。

滚动敏感结果集恰恰相反。滚动敏感结果集使得它方作出的所有更新(这些更新对包含它的事务处理可见)成为可见。然而,插入和删除可能不可见。

让我们仔细定义一下更新可见的含义。如果另外一个事务处理所作的更新影响了某行在结果集中的出现位置删除之后再插入就会出现这种情况,则直到重新打开结果集时才能移动该行。如果某更新使得某行不再是结果集中的成员删除时就是这样,则该行将保持可见,直到重新打开结果集。如果另外一个事务处理显式地删除了某行,则滚动敏感结果集可以为该行保留一个占位符以允许通过绝对位置来逻辑地取出行。然而,更新的列值总是可见的。

DatabaseMetaData 接口提供了确定结果集所支持的确切功能的途径。例如,可以利用新方法:othersUpdatesAreVisible othersDeletesAreVisibleothersInsertsAreVisible 达到这一目的。

只向前结果集实际上是滚动不敏感结果集或滚动敏感结果集(这取决于 DBMS 如何评估生成结果集的查询)的退化形式。大多数 DBMS 对某些查询具有递增地实现查询结果的能力。如果查询结果是递增实现的,则实际上不会检索数据值(直到需要从 DBMS 检索数据值时才检索),因而结果集的行为类似于敏感的结果集。然而,对于另外一些查询,递增实现是不可能的。例如,如果结果集是排过序的,则在 DBMS 将结果集的第一行返回到应用程序之前,需要先生成整个结果集。在这种情况下,只向前结果集的行为就类似于不敏感的结果集。

对于 TYPE_FORWARD_ONLY 结果集, othersUpdatesAreVisibleothersDeletesAreVisible othersInsertsAreVisible 方法可以确定在 DBMS 逐渐递增地实现结果集时插入、更新和删除是否可见。如果一次查询的结果是排过序的,则即使以上方法返回的值为真,也不可能进行递增实现而且变化也不可见。

5.8.3 结果集自身的变化

我们已经指出:由它方作出的变化的可见性通常取决于结果集的类型。与打开的结果集中的变化的可见性有关的最后一点即结果集能否看见自身的变化(插入、更新和删除)。JDBC 应用程序通过调用以下 DatabaseMetaData 方法可以确定结果集所作变化是否对结果集自身可见:ownUpdatesAreVisibleownDeletesAreVisibleownInsertsAreVisible。因为这种能力在 DBMS 和 JDBC 驱动程序之间可能是不同的,所以需要使用以上这些方法。

如果通过在调用 updateXXX() 之后调用 getXXX() 可以检索到更新的列值,则结果集自身的更新就是可见的。如果在调用 updateXXX() 之后,getXXX() 仍然返回最初的列值,则更新就是可见的。同样,如果插入行在调用 insertRow() 之后出现在结果集中,则插入行就是可见的。如果插入行在调用 insertRow() 之后没有立即出现在结果集中(没有关闭结果集或重新打开结果集),则插入行就是不可见的。如果被删除的行不是从结果集中被去掉就是在结果集中留下一个,则删除即是可见的。

下例说明了应用程序如何确定 TYPE_SCROLL_SENSITIVE 结果集能否看见自身的更新。

DatabaseMetaData dmd;
...
if (dmd.ownUpdatesAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE))
{
	// 变化是可见的
}

5.8.4 检测变化

可以调用 ResultSet.wasUpdated()wasDeleted()wasInserted() 方法来分别确定结果集打开以来某行是否受了可见的更新、删除或插入的影响。结果集检测变化的能力与让变化可见的能力互不相关。换句话说,对可见变化的检测并不是自动进行的。

DatabaseMetaData 接口所提供的方法可使应用程序确定某 JDBC 驱动程序是否能为特定结果集类型检测变化。例如,

boolean bool = dmd.deletesAreDetected(
	ResultSet.TYPE_SCROLL_SENSITIVE);

如果 deletesAreDetected 返回 true,则可以使用 ResultSet.wasDeleted() 来检测 TYPE_SCROLL_SENSITIVE 结果集中的“洞”。

5.9 重新取出行

一些应用程序可能需要查看对行所作出的变化。因为 JDBC 驱动程序可以预取并高速缓存从基本数据库读出的数据(参见 ResultSet.setFetchSize()),所以应用程序可能看不见对行作出的最新变化,即使采用敏感的结果集且更新可见。ResultSet.refreshRow() 方法的作用是允许应用程序请求某驱动程序用存储在数据库中的最新数值来刷新某行。如果所取出的尺寸大小大于一行,则 JDBC 驱动程序实际上可以一次刷新多行。应用程序应该尽量限制对 refreshRow() 的调用,因为频繁调用该方法可能会降低性能。

5.10 JDBC 灵活性

尽管我们希望大多数 JDBC 驱动程序能够支持可滚动结果集,但是我们将这种支持设置成是可选的,从而将为不支持可滚动性的数据源实现 JDBC 驱动程序的复杂性降到最小。这样做的目的是使 JDBC 驱动程序可能利用基本数据库系统所提供的支持来为具有这种支持的系统实现可滚动结果集。如果与驱动程序相关联的 DBMS 并不支持可滚动性,则可忽略该功能。JDBC 驱动程序也可以作为 DBMS 上面的一层来实现可滚动性。值得注意的是:JDBC 行集合(属于 JDBC 标准扩展 API 的一部分)总是支持可滚动性,因此当基本 DBMS 不支持可滚动结果集时可以使用行集合。



目录 | 上一页 | 下一页


jdbc@eng.sun.comjdbc-business@eng.sun.com

版权所有 © 1996,1997 Sun Microsystems,Inc. 保留所有权利