JDBC 4.0 里的新东西 JDBCDerbySQL编程XML
JSR 221:JDBC 4.0 API specification
已经出现好几个月了,
这个API规范对JDBC 4.0 的一些新特征进行了详细的说明,里面的内容很多,
下面的译文只是对这些新特征里的一些主要的东西进行了简要的概述,
个人觉得JDBC 4.0距离我们还有点远.
下面的译文有
外刊IT评论
提供
在JDBC 4.0的说明书里列出了大约有20个新的特征和改进,其中一些是主要的,另一些是次要的. 在这篇文章里我不可能把所有的这些都讨论到,我试图以它们提供的功能或者改进的内容将它们分类:
- 驱动器和连接管理
- 异常处理
- 数据类型的支持
- API修改
下面的部分将按这些分类进行更详细的讲解.
驱动器和连接管理
从驱动程序的使用到连接和结果集管理--JDBC的各方面都有着许多新的特征和提高.下面的三个方面是最重要的.
获取连接变的更简易
如果你已经使用JDBC编程有一段时间,我相信你仍然留着为了同目标数据库建立连接而遵循的典型步骤的清单. 这个清单里的第一条应该是加载一个合适的驱动. 你是否想过这个步骤可以改进一下,还是认为就该一直这样? 在JDBC的这个版本了他们确实做了改进. 你再也不需要调用 Class.forName
去显式的加载驱动了. 当你的应用程序第一次试图去连接数据库时,, DriverManager
会自动的把在应用程序的 CLASSPATH
.里发现的驱动加载上. 这是这个版本的JDBC里主要新增的特征之一.
虽然现在 DriverManager
可以自动加载了,但是,建立一个正确数据源对象仍然是推荐的获得连接的方式. 这样具有透明性和可移植性,因为数据源实例的属性可根据需要取得连接的不同数据库而修改. 你不需要为了连上一个不同的数据库实例甚至是一个需要加载不同的驱动的完全不同类型的数据库而改变你的应用程序代码,
更加灵活的 ResultSet
用法
现在 ResultSet
的接口层上提供了几个能促进编程灵活性的重要的特性. RowSet
子接口提供了一个可滚动的, 可更新的, 以及可离线编辑的 ResultSet
. 而 WebRowSet
子接口提供了能够从数据表中读取数据,将之序列化成XML文档,以及读取一个XML文档将之返序列化成结果集的能力 . 这些特征在 OnJava 文章 " Making the Most of JDBC with WebRowSet
."进行了详细的讨论.虽然 RowSet
接口在以前的 JDBC版本中出现过,但当前的版本中 对 SQLXML 数据类型 (以后讨论) 进行了支持,这些特征会使JDBC编程更加简化和灵活.
有更多的API可用了
为了能够访问 SQL:2003 里实现的特征,当前版本的JDBC里增加了一下新的API. 除此之外,有些添加到通用使用接口的方法是用来支持更简化和更好的处理数据的.
现在,让我们来看一下可以运行的代码,讨论一下下面这个 Example1
类的执行结果. 它将会去连接一个嵌入式的Apache Derby 数据库,将查询结果输出到控制台.尽管JDBC 4.0 已经发布了好几个月了,我发现,目前为之只有Apache Derby 这个一个数据库提供了对其支持的驱动,而且是部分实现了JDBC 4.0规范. 这篇文章的所有例子都是基于JDK 1.6 和Apache Derby 10.2.2.0 开发的. 因为是代码样例,我将只会向你显示这个相关讨论所涉及到的真正的Java源文件的一些片段.
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
将会在控制台打印出如下内容. Here is what I got.
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显示了用这种先途径处理异常链的Java 类的部分代码.
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)
通过JDBC4.0,你现在可以使用少许的代码获取并迭代处理异常链了. 在以前的版本中,你必须去单独的获取异常链,并且通过调用 getNextException
来迭代处理异常.
数据类型支持
这个版本的JDBC增加了一些新的数据类型,对其它的类型的支持也有了增强. 我很高兴对XML的支持已经明确了,SQLXML,已经被加入了. 用我的观点,这个接口值得用一个单独的小节来讨论.
SQLXML and XML Support
SQLXML是对应于SQL里的XML数据类型的一种Java语言的数据类型.XML类型是个内置的类型,用于将一个XML值存储成一个表里的一行的一个column值. 缺省的驱动实现了一个SQLXML对象,它是指向XML类型数据的逻辑指针,而不是直接指向数据的. 一个SQLXML对象在它被创建的事务处理过程中有效.
在下面的Examle3类中,我将向你展示如何从当前的连接中创建一个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字段(SQLXML类型)值是Math is Fun 的一行数据插入表中.
我下面将向你展示一个代码片段,它从一个表里读出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接口,用来提供从Java 类里访问ROWID SQL类型的方法.
Enhanced Support for Large Object Types
在JDBC 2.0 版本中增加了Clob
, Blob
, Array
, and Struct接口来支持 CLOB,
在当前的版本中增加了更多的对这这种数据类型的支持. 我将会在 API Changes
部分讨论几个这样的方法.BLOB
, ARRAY
, and STRUCT
等大SQL对象
对国际字符集转换的支持
SQL:2003 提供了对NCHAR
, NVARCHAR
, LONGNVARCHAR
, and NCLOB
.等数据类型的支持. 它们除了类似于 CHAR
, VARCHAR
, LONGVARCHAR
, and CLOB
之外还能够用不同的字符集对值进行编码.,这被称做国际字符集 (NCS). 如果你的数据需要扩展的字符处理操作,你就不能使用常规的数据类型,而是要用NCS数据类型. 这个版本的JDBC增加了一些对NCS的新的支持,还对有些支持进行了强化.
- 向PreparedStatement
,
CallableStatement
, andResultSet
接口里增加了一些setter和update方法来支持NCS转换.例如setNString
,setNCharacterStream
, andsetNClob
等方法就是这些新增的里的几个. - 在
SQLInput
andSQLOutput
接口里增加了读和写的方法来支持NClob
andNString
对象.
API Changes
JDBC 4.0 里的强化的特征大多存在于API层,下面这小节会对此进行简短的讨论. 更多的细节请参考 JSR 221:JDBC 4.0 API specification .
Array
在Array接口里增加了一个'释放'方法用于释放array对象,并释放它占有的资源.
Connection
and PooledConnection
Connection
接口现在提供了一批用来创建大对象比如 createClob
, createBlob之类的方法
. 其他的增加是对针对客户端信息的getter和setter方法的版本信息的覆盖,以及当前连接的有效性的检查.
现在 PooledConnection
接口提供了 addStatementEventListener
and removeStatementEventListener 方法
,当你需要去注册和反注册一个 StatementEventListener
接口时,有了它事情会变得很容易, StatementEventListener
接口是在当前版本里引进的. 当 Statement
pool.里的 PreparedStatement
s 有事件发生时,这个接口的注册者的实例会被通知. 例如,当一个声明里的PreparedStatement
被关闭时,如果有过注册,驱动器就会调用所有StatementEventListener里的statementClosed方法.
DatabaseMetaData
不同的关系数据库通常是支持不同的特征,用不同的方式实现特征,以及使用不同的数据类型. 这个会引起可移植性的问题,因为在一种数据库上工作的代码由于数据库底层实现的不同而不能在其他数据库上运行. 这些问题可以通过调用这个接口里方法获得一些扩展的使用信息来解决. 例如,假设你写一些代码通过传入一个SQL语句来创建一个表. 你可以通过调用这个接口里的 getTypeInfo 方法来确定那些数据类型可以在CREATE TABLE 语句里使用.
这个版本的JDBC里增加了不少用来支持查询数据库原信息的方法. 在 Example4中,我用一个代码片段来演示用这个新功能来获得符合典型搜索条件的数据库 schemas 清单.
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开头的schemas. 下面是我得到的table schema清单:
SYS
SYSCAT
SYSCS_DIAG
SYSCS_UTIL
SYSFUN
SYSIBM
SYSPROC
SYSSTAT
标量函数支持
一个标量函数处理一个输入的预定义的集合,返回处理结果. 例如调用标量函数 ABS(number)会返回number的绝对值.
这些函数可以作为Java代码里的SQL句子的一部分. 如果底层的数据库支持下面的新标量函数: CHAR_LENGTH
, CHARACTER_LENGTH
, CURRENT_DATE
, CURRENT_TIME
, CURRENT_TIMESTAMP
, EXTRACT
, OCTET_LENGTH
, and POSITION,这个版本的JDBC就需要它的驱动实现对这些的支持.
Statement
, PreparedStatement
, and CallableStatement
Statement接口现在提供了一个isClosed方法来查询statement是否被关闭了,一个setPoolable方法,当你想让这个statement被poolable或者non-poolable是用它来设置,一个isPoolable方法来检查当前的statement是否是poolable的.
PreparedStatement
and CallableStatement
接口提供了更多的方法来通过InputStream
and Reader
插入大数据对象.
Wrapper
这个版本的API中增加了一个Wrapper接口,这接口提供了一个用于访问一些由于架构的原因而被封装的资源的实例的机制. 这个封装范式,也叫做 Adapter pattern ,被许多的JDBC实现所使用,用来提供基于传统的提供给数据源的JDBC API的扩展功能. 这个接口的主要意图是避免用非标准的方式来访问厂商特有的资源. 在这个接口的实例上调用unwrap方法,你会得到一个实现了这个接口的对象. 因为这个unwrap方法是个十分耗资源的操作,你在unwarping之前应该使用这个接口里的isWrapperFor方法来检查这个实现了接口的class是间接的还是直接的封装了某部对象.