Java 数据库连接(JDBC)3.0 规范建立在其原本稳固的基础上,增加了几个新功能以弥补原来功能不足的地方。无论是
java.sql 还是第一次出现的
javax.sql 软件包,都会包含在还处于测试阶段的 Java 1.4 版平台中。在今年晚些时候它就会被正式发布,到时 Java 开发人员就能够利用这些改进了,所以现在正是开始了解这些改变的好时候。
我们会简单地讨论一下 JDBC 的设计师们为这个版本所考虑到的几个设计目标。理解了设计师们的设计基本原理,我们就可以更好地去理解那些改变。我们会总结一下规范中的几个新功能以便了解整个 API 是怎样被改变的。另外,我们还会深入研究几个最适用于应用程序开发人员的关键功能,以成功地协助您利用其新性能。
设计 JDBC 3.0 规范的初衷主要是让原先的 JDBC 规范下的功能更加完美。因此,这个新规范的设计指导原则之一就是要与现存的应用程序和驱动程序保持兼容性。所以,JDBC 2 的用户可以放心,他们的应用程序能在 JDBC 3.0 下正确运行。另外,使用以前那些遭反对的方法写进 JDBC 1 API 的代码也可以继续运行。
随着 J2EE 平台迅速的日益流行,设计师们也想增强 JDBC 的可伸缩性。新增的语句池和增强的连接池支持离实现这个目标还很远。此外,设计师们还仔细地考虑 JDBC 与新的连接器体系结构之间的关系,来继续提高服务器上的 Java 技术。
在 JDBC 2 开发的过程中,SQL99 还处在一种变化不定的情况下。现在规范已经完成了,而且数据库厂商已经采用了部分标准。所以自然地,JDBC 规范就跟着将自己与 SQL99 功能的一部分相统一。最新的 JDBC 规范已经采用了 SQL99 标准中那些已经被广泛支持的功能,还有那些在五年内可能会获得支持的功能。
如果一个数据库还不支持 JDBC 3.0 所支持的部分 SQL99 功能,驱动程序可以使用元数据 API 向应用程序开发人员表明:其底层数据库不支持一部分 JDBC 功能。这一点允许数据库厂商生产出相应的 JDBC 驱动程序,尽管他们可能不支持所有的功能。增加的两种新的数据类型以及对事务的 Savepoint 的支持说明了两个和 SQL99 有关的改变。
元数据 API 已经得到更新,
DatabaseMetaData 接口现在可以检索 SQL 类型的层次结构,一种新的
ParameterMetaData 接口可以描述
PreparedStatement 对象中参数的类型和属性。
在 JDBC 3.0 之前,设置一个存储过程中的一个参数要指定它的索引值,而不是它的名称。
CallableStatement 接口已经被更新了,现在您可以用名称来指定参数。
JDBC 所支持的数据类型作了几个改变,其中之一是增加了两种新的数据类型。
为了便于修改 CLOB(Character Large OBject,字符型巨对象)、BLOB(Binary Large OBject,二进制巨对象)和 REF(SQL 结构)类型的值,同名的数据类型接口都被更新了。接下来的是,因为我们现在能够更新这些数据类型的值,所以
ResultSet 接口也被修改了,以支持对这些数据类型的列的更新,也包括对 ARRAY 类型的更新。
增加的两种新的数据类型是
java.sql.Types.DATALINK 和
java.sql.Types.BOOLEAN 。新增的数据类型指的是同名的 SQL 类型。DATALINK 提供对外部资源的访问或 URL,而 BOOLEAN 类型在逻辑上和 BIT 类型是等同的,只是增加了在语义上的含义。DATALINK 列值是通过使用新的
getURL() 方法从
ResultSet 的一个实例中检索到的,而 BOOLEAN 类型是通过使用
getBoolean() 来检索的。
为了解决对获取自动产生的或自动增加的关键字的值的需求,JDBC 3.0 API 现在将获取这种值变得很轻松。要确定任何所产生的关键字的值,只要简单地在语句的
execute() 方法中指定一个可选的标记,表示您有兴趣获取产生的值。您感兴趣的程度可以是
Statement.RETURN_GENERATED_KEYS ,也可以是
Statement.NO_GENERATED_KEYS 。在执行这条语句后,所产生的关键字的值就会通过从
Statement 的实例方法
getGeneratedKeys() 来检索
ResultSet 而获得。
ResultSet 包含了每个所产生的关键字的列。清单 1 中的示例创建一个新的作者并返回对应的自动产生的关键字。
清单 1. 检索自动产生的关键字
Statement stmt = conn.createStatement();
// Obtain the generated key that results from the query.
stmt.executeUpdate("INSERT INTO authors " +
'(first_name, last_name) " +
"VALUES ('George', 'Orwell')",
Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys();
if ( rs.next() ) {
// Retrieve the auto generated key(s).
int key = rs.getInt();
}
|
大多数应用程序开发人员不需要知道 JDBC 和 J2EE 连结器体系结构之间的关系,就可以很好地使用 JDBC API。但是,由于 JDBC 3.0 规范已经考虑到这项新的体系结构,这使得开发人员能更好地理解 JDBC 在哪里适合 J2EE 标准,以及这个规范的发展方向是什么。
J2EE 连结器体系结构指定了一组协议,允许企业的信息系统以一种可插入的方式连接到应用服务器上。这种体系结构定义了负责与外部系统连接的资源适配器。连接器服务提供者接口(The Connectors Service Provider Interface,SPI)恰好和 JDBC 接口提供的服务紧密配合。
JDBC API 实现了连结器体系结构定义的三个协议中的两个。第一个是将应用程序组件与后端系统相连接的连接管理,它是由
DataSource 和
ConnectionPoolDataSource 接口来实现的。第二个是支持对资源的事务性访问的事务管理,它是由
XADataSource 来处理的。第三个是支持后端系统的安全访问的安全性管理,在这点上,JDBC 规范并没有任何对应点。尽管有最后那个不足,JDBC 接口仍能映射到连接器 SPI 上。如果一个驱动程序厂商将其 JDBC 驱动程序映射到连接器系统协议上,它就可以将其驱动程序部署为资源适配器,并立刻享受可插性、封装和在应用服务器中部署的好处。这样,一个标准的 API 就可以在不同种类的的企业信息系统中,供企业开发人员使用。
一个可保持的游标(或结果),就是说该游标在包含它的事务被提交后,也不会自动地关闭。JDBC 3.0 增加了对指定游标可保持性的支持。要制定您
ResultSet 的可保持性,您必须在使用
createStatement() 、
prepareStatement() 或
prepareCall() 方法准备编写一条语句时就这么做。可保持性可以是下面常量中的一个。
HOLD_CURSORS_OVER_COMMIT
|
ResultSet 对象(游标)没有被关闭;它们在提交操作得到显式的或隐式的执行以后仍保持打开的状态。
|
CLOSE_CURSORS_AT_COMMIT
|
ResultSet 对象(游标)在提交操作得到显式的或隐式的执行后被关闭。
|
总的来说,在事务提交之后关闭游标操作会带来更好的性能。除非您在事务结束后还需要该游标,否则您最好在执行提交操作后将其关闭。因为规范没有规定
ResultSet 的缺省的可保持性,所以具体行为还将取决于执行情况。然而,我希望在可以使用 JDBC 3.0 驱动程序时,大多数执行在事务结束后仍旧会关闭游标。
JDBC 2 规范的一个局限是,在任意时刻,返回多重结果的语句只能打开一个
ResultSet 。作为 JDBC 3.0 规范中改变的一个部分,规范将允许
Statement 接口支持多重打开的
ResultSets 。然而,重要的是
execute() 方法仍然会关闭任何以前
execute() 调用中打开的
ResultSet 。所以,要支持多重打开的结果,
Statement 接口就要加上一个重载的
getMoreResults() 方法。新式的方法会做一个整数标记,在
getResultSet() 方法被调用时指定前一次打开的
ResultSet 的行为。接口将按如下所示定义标记:
CLOSE_ALL_RESULTS
| 当调用
getMoreResults() 时,所有以前打开的
ResultSet 对象都将被关闭。
|
CLOSE_CURRENT_RESULT
| 当调用
getMoreResults() 时,当前的
ResultSet 对象将被关闭。
|
KEEP_CURRENT_RESULT
| 当调用
getMoreResults() 时,当前的
ResultSet 对象将不会被关闭。
|
清单 2 展示的是一个处理多重打开结果的示例。
清单 2. 如何处理多重打开结果
String procCall;
// Set the value of procCall to call a stored procedure.
// ...
CallableStatement cstmt = connection.prepareCall(procCall);
int retval = cstmt.execute();
if (retval == false) {
// The statement returned an update count, so handle it.
// ...
} else { // ResultSet
ResultSet rs1 = cstmt.getResultSet();
// ...
retval = cstmt.getMoreResults(Statement.KEEP_CURRENT_RESULT);
if (retval == true) {
ResultSet rs2 = cstmt.getResultSet();
// Both ResultSets are open and ready for use.
rs2.next();
rs1.next();
// ...
}
}
|
JDBC 3.0 定义了几个标准的连接池属性。开发人员并不需要直接地用 API 去修改这些属性,而是通过应用服务器或数据存储设备来实现。由于开发人员只会间接地被连接池属性的标准化所影响,所以有利之处并不明显。然而,通过减少厂商特定设置的属性的数量并用标准化的属性来代替它们,开发人员能更容易地在不同厂商的 JDBC 驱动程序之间进行交换。另外,这些属性还允许管理员很好地优化连接池,从而使应用程序的性能特点发挥到极致。这些属性如下表所示。
| 属性名称 | 描述 |
maxStatements
| 连接池可以保持打开的语句数目。 |
initialPoolSize
| 当池初始化时可以建立的物理连接的数目。 |
minPoolSize
| 池可以包含的物理连接的最小数目。 |
maxPoolSize
| 池可以包含的物理连接的最大数目。零指没有最大值。 |
maxIdleTime
| 持续时间,以秒计,指一个闲置的物理连接在被关闭前可以在池中停留的时间。零指没有限制。 |
propertyCycle
| 间隔时间,以秒计,指连接池在执行其属性策略前可以等待的时间。 |
除了改进对连接池的支持以外,现在也能缓冲预备语句了。预备语句允许您用一条常用的 SQL 语句然后预编译它,从而在这条语句被多次执行的情况下大幅度地提升性能。在另一个方面,建立一个
PreparedStatement 对象会带来一定量的系统开销。所以,在理想情况下,这条语句的生命周期应该足够长,以补偿它所带来的系统开销。追求性能的开发人员有时候为了延长
PreparedStatement 对象的生命周期会不惜扭曲他们的对象模型。JDBC 3.0 让开发人员不再为此担心,因为数据源层现在负责为预备语句进行缓存。
清单 3 将示范如何利用 JDBC 对预备语句池的支持。细心的读者可能会发现清单中的语句和普通 JDBC 2 的代码没什么两样。这是因为语句的缓冲是完全在内部实现的。这就意味着,在 JDBC 3.0 下,您现存的代码可以自动利用语句池。但可惜的是,这也意味着您将不能控制哪个预备语句将被缓冲,而只能控制被缓存的语句的数目。
清单 3. 缓冲预备语句
String INSERT_BOOK_QUERY = "INSERT INTO BOOKLIST " +
'(AUTHOR, TITLE) " +
"VALUES (?, ?) ";
Connection conn = aPooledConnection.getConnection();
PreparedStatement ps = conn.prepareStatement(INSERT_BOOK_QUERY);
ps.setString(1, "Orwell, George");
ps.setString(2, "1984");
ps.executeUpdate();
ps.close();
conn.close();
// ...
conn = aPooledConnection.getConnection();
// Since the connection is from a PooledConnection, the data layer has
// the option to retrieve this statement from its statement pool,
// saving the VM from re-compiling the statement again.
PreparedStatement cachedStatement = conn.prepareStatemet(INSERT_BOOK_QUERY);
// ...
|
也许在 JDBC 3.0 中最令人兴奋的附加特点就是 Savepoint 了。JDBC 2 中的事务支持让开发人员可以控制对数据的并发访问,从而保证持续数据总是保持一致的状态。可惜的是,有时候需要的是对事务多一点的控制,而不是在当前的事务中简单地对每一个改变进行回滚。在 JDBC 3.0 下,您就可以通过 Savepoint 获得这种控制。
Savepoint 接口允许您将事务分割为各个逻辑断点,以控制有多少事务需要回滚。图 1 将说明如何在事务中运用 Savepoint。
图 1. Savepoint 的直观表示
您或许不是经常需要使用 Savepoint。然而,在一种普遍的情况下 Savepoint 会发挥作用,那就是您需要作一系列的改变,但是在知道所有的结果之前不能确定应该保留这些改变的哪一部分。清单 4 中的代码示例说明了如何使用
Savepoint 接口。
清单 4. 使用 Savepoint
conn.setAutoCommit(false);
// Set a conservative transaction isolation level.
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate( "INSERT INTO authors " +
'(first_name, last_name) VALUES " +
'('Lewis', 'Carroll')");
// Set a named savepoint.
Savepoint svpt = conn.setSavepoint("NewAuthor");
// ...
rows = stmt.executeUpdate( "UPDATE authors set type = 'fiction' " +
"WHERE last_name = 'Carroll'");
// ...
conn.rollback(svpt);
// ...
// The author has been added, but not updated.
conn.commit();
|
JDBC 3.0 现在正在测试期中,官方发行定在 2001 年下半年。主要的数据库厂商正在致力于提供 JDBC 3.0 的驱动程序,一些早期的测试版驱动程序已经可以获得。JDBC 3.0 的改变虽然在本质上不是革命性的,但也是一个非常重要的进步。通过在现有功能上的扩展,新的 JDBC 规范带给您的是新的策略,以解决您的关系数据库的问题。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 加入本文的
讨论论坛。
- 请阅读 JDBC 规范或下载最新的
JDBC API。
- 请阅读
连接器体系结构规范。
- 请访问
J2EE 主页。
- 请访问
J2SE 1.4 测试版主页。
- John Zukowski 在他正在进行中的 DeveloperWorks 专栏,
Merlin 的魔力中涉及了一些 Java 1.4 测试版中的精选的重要部分。
- 在六月份的 JavaOne 会议上,John Zukowski 参加了几场 Java 1.4 测试版(Merlin)的报告,在
“Java 1.4,下一波”中阐述了 developerWorks 的重要部分。
- IBM 红皮书
“IBM WebSphere and VisualAge for Java Database Integration with DB2, Oracle, and SQL Server”描述了怎样将关系数据库系统与 Web 服务器集成。
- Lennart Jrelid 在
“Use JDBC for industrial-strength performance,第 1 部分”(developerWorks,2000 年 1 月)中,讨论了通过 JDBC 来使用服务器端的 Java 模式。在
第 2 部分(developerWorks,2000 年 2 月),他描述了使用面向批量数据技术来得到多重 ResultSet 对象的方法。
- 请访问
developerWorks Java 技术专区,获得更多的 Java 参考资料。

Josh Heidebrecht 是一位 Sun 认证的 Java 2 平台程序员,他在卡尔加里大学获得计算机科学的学位。他目前是多伦多 IBM 软件实验室 VisualAge for Java 的开发小组成员。晚上他会学弹古典吉他而将邻居吓跑。可通过 jheidebr@ca.ibm.com联系 Josh。