[译]JDBC4.0具有哪些新特性?
在 Java SE 6 所提供的诸多新特性和改进中,值得一提的是为 Java 程序提供数据库访问机制的 JDBC 版本升级到了 4.0, 这个以 JSR-221 为代号的版本 , 提供了更加便利的代码编写机制及柔性 , 并且支持更多的数据类型 . 在本文中,我们将从编码的易用性及柔性的角度探讨 JDBC 4.0 所带来的新特性及改进。
JDBC 4.0 的新特性
JDBC 4.0 文档列举了 20 个改进及新特性 , 大小不等 . 本文无法做到尽述其详 , 为此笔者根据其功能特点及应用领域将其分为下述四类:
1. 驱动及连接管理
2. 异常处理
3. 数据类型支持
4. API 的变化
下面按照上述四类展开详述:
驱动及连接管理
驱动及连接的使用和结果集管理 — 在 JDBC 的很多地方都发生了显著的变化
连接数据库变得更加容易
如果您曾经有过 JDBC 开发的经验 , 那么我确信您还保存着一份建立数据库连接所必须的工作列表 . 而列表中的第一项即加载一个合适的驱动程序 . 您是否想过这个步骤应该被改进呢 ? 在此版 JDBC 中做到了 . 您不必再显式地加载 Class.forName 了 . 当您的程序首次试图连接数据库时 , DriverManager 自动加载驱动到当前应用的 CLASSPATH . 这是 JDBC 的一个比较大的改动 .
尽管 DriverManager 现在可以自动地加载驱动 , 建立一个 DataSource 对象 仍是获取连接的推荐的方法 . 因为可以在配置中将数据源指向不同的数据库, DataSource 更具透明性和灵活性。 这样就可以访问另一个数据库实例不需更改现有的任意一行代码 , 甚至数据库的驱动完全不同也没有关系 .
ResultSet 的使用变得更为灵活
ResultSet 接口的层次结构当前为编程的灵活性提供了一些新的机制 . RowSet 子接口滚动、可提交并可离线编辑的 ResultSet . WebRowSet 子接口提供了从数据库表中获取数据 , 并将其序列化为 XML 文档 , 抑或是将 XML 解析成 result set 的能力 . 尽管上个版本的 JDBC 也提供了 RowSet 接口层次 ,当前的版本对于 SQLXML 数据类型支持得更好 ( 稍后讨论 ) ,这些特性是的 JDBC 编程更加容易且具灵活性 .
提供了更多的 API
本版 JDBC 提供了更多的 API 以实现访问 SQL:2003 具备的新特性 . 此外 , 为了更好的操纵修改数据, JDBC 也增添了许多的方法。 .
现在我们来看一些代码并讨论下面 Example1 类的输出结果 . 它将连接嵌入式的 Apache Derby 数据库并在控制台上显示输出结果 . 尽管 JDBC 4.0 已推出几个月了 , 笔者发现只有 Apache Derby 提供了支持 JDBC 4.0 规范的驱动 ( 截至 2007 年 3 月 ). 本文的所有例子均用 JDK 1.6 和 Apache Derby 数据库 10.2.2.0 开发 .
public class Example1 {
public static void main(String[] args) {
...
String dbName = "example1";
String tableName = "stu1";
ds = new EmbeddedDataSource40();
ds.setDatabaseName(dbName);
String connectionURL = "jdbc:derby:"+dbName+";create=true";
try {
con = ds.getConnection();
stmt = con.createStatement();
rs = stmt.executeQuery("select * from "+tableName);
int colCount= rs.getMetaData().getColumnCount();
for (int j=0; j< colCount; j++){
System.out.print(rs.getMetaData().getColumnName(j+1)
+ "\t");
}
while (rs.next()) {
System.out.print("\n");
for (int i = 0; i < colCount; i++) {
System.out.print(rs.getString(i + 1) + "\t");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
finally{
//close connections
}
}
}
如果在 example1 数据库的 stu1 表中有数据的话 , 编译并运行 Example1.java 将在控制台获得以下输出:
ID NAME COURSE
1001 John Doe Statistics
1002 Jack McDonalds Linear Algebra
如果想看 DriverManager 如何自动加载 JDBC 驱动 , 可以将 Example1 中的: con=ds.getConnection() 替换为: con=DriverManager.getConnection(connectionURL) . 该类将产生相同的输出。正如您所看到的,再也不用显式地调用 Class.forName() 了 .
异常处理
怎样辨别一个 Java 程序的健壮与否呢 ? 在我看来,异常处理机制是重要的考虑因素之一 . 一个健壮的 Java 程序可以很好地处理异常 , 并给予程序在发生异常时恢复的能力 . 而一个不健壮的程序将导致输出错误的结果甚至导致整个应用的崩溃 !
JDBC 4.0 增加了一些简单而有力的异常处理机制 , 其中值得一提的是链式异常,如果这个异常链存在的话,即可应用增强了的 for - each 循环来获取异常链 ,. 下面的 Example2 类的局部结构展示了如何应用这种新的方法处理链式异常:
public class Example2 {
public static void main(String[] args) {
String dbName = "example";
String tableName = "student4";
try {
con = ds.getConnection();
stmt = con.createStatement();
rs = stmt.executeQuery("select * from " + tableName);
} catch (SQLException sx) {
for(Throwable e : sx ) {
System.err.println("Error encountered: " + e);
}
}
finally{
//close connections
}
}
}
运行 Example2.java , 注意 student4 并不是数据库中实际存在的表 . 将在下列调用中产生链式异常:
rs = stmt.executeQuery("select * from " + tableName);
在实际的应用中,需要捕捉到这些异常 , 检测并进行相应的处理 . 在本例中,笔者仅将其在控制台输出 . 以下是输出代码:
for(Throwable e : sx ) {
System.err.println("Error encountered: " + e);
}
以下是类 Example2 输出的结果 :
Error encountered: java.sql.SQLSyntaxErrorException:
Table/View 'STUDENT4' does not exist.
Error encountered: java.sql.SQLException:
Table/View 'STUDENT4' does not exist.
Exception in thread "main" java.lang.NullPointerException
at ex.Examlpe2.main(Examlpe2.java:51)
通过应用 JDBC 4.0, 您现在不需太多代码即可以获取及遍历异常链 . 在以往的版本中 , 您在遍历异常链时,必须手工的调用 getNextException 方法才能得到相同的效果 .
支持的数据类型
本版 JDBC 增加了一些新的数据类型,对其他的一些数据类型,则提供了更好的支持 . 笔者为 XML 被正式支持感到欣喜 , 本版中产生了一个新的接口 : SQLXML . 在笔者看来这个接口值得单独开一个章节为其讨论:
SQLXML 与 XML 的支持
SQLXML 是 SQL 中 XML 数据类型在 Java 中的表示, XML 是 SQL 中用于表示表中 XML 数据的内建数据类型 . 在默认的情况下, JDBC 驱动将 SQLXML 指针指向 XML 数据而不是数据本身 . SQLXML 对象在其被创建的事务中是稳定的 .
在下面的 Example3 类中 , 笔者将说明如何在当前连接中应用 SQLXML 并更新表数据 .
public class Example3 {
public static void main(String[] args) {
...
con = ds.getConnection();
SQLXML sx= con.createSQLXML();
sx.setString("Math is Fun");
String psx ="insert into "+tableName+
" ( id, textbook) values(?,?) ";
PreparedStatement pstmt = con.prepareStatement(psx);
pstmt.setString(1,"1000");
pstmt.setSQLXML(2,sx);
pstmt.executeUpdate();
...
}
}
这个例子说明了您所能应用的最简单的情况 . 如果我们继续深入研究,事情就会变得有趣得多了 . 但在我们深入讨论之前 , 让我来告诉您运行 Example3.java . 的结果。 非常不幸 , 我无法获取到 SQLXML 对象,并得到了以下让人失望的输出:
java.sql.SQLFeatureNotSupportedException: Feature not
implemented: no details.
at org.apache.derby.impl.jdbc.SQLExceptionFactory40.
getSQLException(Unknown Source)
... ... ... ...
at ex.Example3.main(Example3.java:62)
看来 Apache Derby 并没有提供从 Connection 中获取 SQLXML 对象的方法 . 但至少您可以看到笔者正试图在类 Example3 中实现的东西 : 我想插入一行新的数据: id 列值为 1000 textbook 列 (S QLXML 类型 ) 插入 Math is Fun .
笔者用如下代码段结束关于 SQLXML 的讨论,这段代码从数据库中读取 XML 值并将其转化为 Document 对象 .
SQLXML sqlxml = rs.getSQLXML(column);
InputStream binaryStream = sqlxml.getBinaryStream();
DocumentBuilder parser =
DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = parser.parse(binaryStream);
可以把一个列的值直接转化为 XML 文档不是一件令人兴奋的事情吗 ? 我觉得这个特性非常好 .
ROWID 数据类型
SQL ROWID 唯一标识了数据表中的一行,并是访问该行的最快的方法, 本版增加了 RowId 接口以提供对 ROWID SQL 数据类型在 Java 类中的支持 .
对大对象类型支持的增强
JDBC 版本 2 提供了对大的 SQL 对象如: CLOB , BLOB , ARRAY 的支持 , 及用于添加相关接口的 Struct: Clob , Blob , Array , and Struct . 在本版的 JDBC 中增加了很多对这些对象访问的新方法 . 笔者将在 API 变化一节中进行详细论述 .
支持 National Character Set ( NCS ) 转化
SQL:2003 提出了如下 SQL 数据类型的支持: NCHAR , NVARCHAR , LONGNVARCHAR , 及 NCLOB . 其功用和 CHAR , VARCHAR , LONGVARCHAR , 及 CLOB 类似 ,其区别仅是,这些类型的文本是用 NCS 编码的。 如果需要大量的字符处理,您可能更倾向于 NCS 数据类型而非普通的数据类型。本版 JDBC 提供了增强对 NCS 支持的 API.
- 在 PreparedStatement , CallableStatement , 及 ResultSet 接口中增加了一些 setter 和 updater 方法以支持 NCS 转化 . 比如方法 setNString , setNCharacterStream , 及 setNClob 等等 .
- 在 SQLInput and SQLOutput 接口中增加了读写方法以支持 t NClob 和 NString 对象 .
API 变化
JDBC 4.0 最大的变化来自于 API, 笔者在本小节对其做简单介绍 .
Array
Array 接口增加了一个 free 方法来释放 array 对象及其持有的资源 .
Connection 和 PooledConnection
Connection 接口现在提供一系列创建大对象的方法如 createClob , createBlob 等等 . 此外还有 getter 和 setter 对客户端信息的重载方法 , 及验证当前连接正确性的方法 .
PooledConnection 接口当前提供 addStatementEventListener 和 removeStatementEventListener 两个方法来注册和注销 StatementEventListener 接口 , 这个接口是在本版 JDBC 中新引入的 . 这个接口的一个实例将获取到 S tatement 池中 PreparedStatement s 的变化 . 比如,在注册以后 , 当驱动调用 statementClosed 方法时,所有 StatementEventListener 将获得 statement 已关闭的通知 .
DatabaseMetaData
不同的关系数据库往往支持不同的特性 , 并通过不同的方法来实现这些特性 , 并可能会是用不同的数据类型 . 这将会导致移植性的问题,因为根据实现的不同,无法保证代码在所有关系数据库上都能正确执行 . 这样的问题在一定程度上可以通过这个接口所获得的信息来解决 . 比如,如果您在写一个通过传入 SQL 语句来建立表的代码 . 您可能想知道在 CREATE TABLE 语句中有哪些数据类型是可用的,此时可以调用该接口中的 getTypeInfo 方法 .
本版 JDBC 增加了一些获取信息的方法 . 在 Example4 中 , 我将通过一段代码展示如何获得满足某种模式的数据库结构的列表。 .
con = ds.getConnection();
DatabaseMetaData dmd = con.getMetaData();
rs=dmd.getSchemas("TABLE_CAT", "SYS%");
//iterate over the rs and print to console
首先通过调用 dmd.getCatalogs 并遍历结果集 , 得到了唯一的一个值: TABLE_CAT . 接着通过调用 rs=dmd.getSchemas("TABLE_CAT", "SYS%") 得到以 SYS 开头的数据库和表结构 . 以下是笔者得到的结果 :
SYS
SYSCAT
SYSCS_DIAG
SYSCS_UTIL
SYSFUN
SYSIBM
SYSPROC
SYSSTAT
Scalar 函数支持
一个 scalar 函数操作预定义的输入数据集合并返回结果 . 比如: scalar 函数调用 ABS(number) 返回 number 的绝对值 . 这些 scalar 函数可以作为 SQL 字符串的一部分来使用 . 本版 JDBC 要求当所依赖的关系数据库支持以下功能时: CHAR_LENGTH , CHARACTER_LENGTH , CURRENT_DATE , CURRENT_TIME , CURRENT_TIMESTAMP , EXTRACT , OCTET_LENGTH , 和 POSITION ,驱动必须实现这些功能。
Statement , PreparedStatement , 和 CallableStatement
Statement 接口当前提供 isClosed 方法来判断 statement 是否已关闭 , setPoolable 用来设置是否可以被池化 , 用 isPoolable 来检测当前的池化状态。
PreparedStatement 及 CallableStatement 接口现在提供了更多插入大对象的方法 , 通过使用 InputStream 及 Reader 等 .
Wrapper
这个版本的 API 增加了一个新的 Wrapper 接口, 来提供一种访问资源的实例的方法 , 这可能是基于架构的考虑 . Wrapper 模式 , 被许多的 JDBC 驱动实现应用以提供 JDBC API 之外的依赖于具体数据源的应用 . 这个接口的主要目的是用来提供供应商相关的功能。您可以通过调用 unwrap 方法来获取到数据库连接的接口实现的实例 . 因为这是一个重量级的操作 , 在使用前,应该先调用 isWrapperFor 方法来检测是否当前实例是某种实现的一个间接或直接的 Wapper
能够给出一个程序例子当然是最好的,但是 Apache Derby 参考手册 l 指出 : "JDBC 4.0 引入了 wrapped JDBC 对象的概念 ... 对于 Derby 来说 , 这对 Derby 来说是没有意义的,因为 Derby 并不做规范之外的扩展 ." 因此看来这种尝试也就变得无甚必要了 !
结论
我们已经分为 4 类讨论了 JDBC 4.0 所做的一些改进和新的特性,这些新特性增加了编程易用性,提高了生产率 . 尽管 API 规范已经推出几个月了 , 到笔者截稿时,主流的数据库厂商都没有提供本版的 JDBC 驱动 . 当更多的供应商开始支持 JDBC 4.0 时 — 当然也包括您所中意的那个 — 您就可以享受 JDBC4.0 所提供的这些易用的功能了 .
最后,我认为有一个各大数据库厂商的支持的 JDBC 版本的列表是必要的 . Sun Developer Network (SDN) 上有一个 JDBC Data Access API ( http://developers.sun.com/product/jdbc/drivers ) 页提供了一份更新不太及时的列表 .
原作者信息:
Sharad Acharya has more than eight years of experience in the software engineering field in multiple business domains including supply chain, insurance, banking, and mortgage.
@2008 杨一. 版权所有. 保留所有权利