[Onejava Studio Web][JAVA]结果集增强功能
本章讨论 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 驱动程序创建使用该驱动程序所不支持的结果集类型的 Statement、PreparedStatement 或 CallableStatement 对象。这种情况下,驱动程序应该在生成该语句的 Connection 上发出 SQLWarning,并且依据以下原则为该语句的结果集类型选择另外一个替代值:

如果应用程序要求可滚动的结果集,则驱动程序应该采用它所支持的可滚动类型,即使该类型不同于应用程序所请求的确切类型。
如果应用程序要求可滚动的结果集,而驱动程序又不支持可滚动性,则驱动程序应该采用只向前结果集类型。同样,也可以调用方法 DatabaseMetaData.supportsResultSetConcurrency() 来确定某驱动程序支持何种并发类型。如果应用程序向 JDBC 驱动程序要求它所不支持的并发类型,则驱动程序应该在生成该语句的 Connection 上发出 SQLWarning,并且选择另外一种替代并发类型。如果应用程序既指定了不支持的结果集类型又指定了不支持的并发类型,则应该首先选择结果集类型。
某些情况下,JDBC 驱动程序可能需要在语句执行时为 ResultSet 选择另外一个结果集类型或并发类型。例如,包含多表有连接的 SELECT 语句不能生成可更新的 ResultSet。这种情况下,JDBC 驱动程序应该在生成 ResultSet 的 Statement、PreparedStatement 或 CallableStatement 上发出 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 驱动程序生成可更新的结果集。然而,开发人员通常可以期望满足以下准则的查询来生成可更新的结果集:

查询只引用了数据库中的单个表。
查询不含任何连接操作。
查询选择它所引用的表的主键。 另外,如果要执行插入操作,则 SQL 查询还应该满足以下所列条件。
查询选择基本表中的所有非可空列。
查询选择没有缺省值的所有列。