年底Sun 公司发布了 Java Standard Edition (Java SE )的最终正式版代号 Mustang(野马)跟 Tiger(Java SE )相比Mustang 在性能方面有了不错的提升与 Tiger 在 API 库方面的大幅度加强相比虽然 Mustang 在 API 库方面的新特性显得不太多但是也提供了许多实用和方便的功能在脚本WebServiceXML编译器 API数据库JMX网络和 Instrumentation 方面都有不错的新特性和功能加强 本系列 文章主要介绍 Java SE 在 API 库方面的部分新特性通过一些例子和讲解帮助开发者在编程实践当中更好的运用 Java SE 提高开发效率
本文是系列文章的第 篇介绍了 Java SE 在数据库编程方面的新特性
Java DBJava 里的数据库
新安装了 JDK 的程序员们也许会发现除了传统的 binjre 等目录JDK 新增了一个名为 db 的目录这便是 Java 的新成员Java DB这是一个纯 Java 实现开源的数据库管理系统(DBMS)源于 Apache 软件基金会(ASF)名下的项目 Derby它只有 MB 大小对比动辄上 G 的数据库来说可谓袖珍但这并不妨碍 Derby 功能齐备支持几乎大部分的数据库应用所需要的特性更难能可贵的是依托于 ASF 强大的社区力量Derby 得到了包括 IBM 和 Sun 等大公司以及全世界优秀程序员们的支持这也难怪 Sun 公司会选择其 版本纳入到 JDK 中作为内嵌的数据库这就好像为 JDK 注入了一股全新的活力Java 程序员不再需要耗费大量精力安装和配置数据库就能进行安全易用标准并且免费的数据库编程在这一章中我们将初窥 Java DB 的世界来探究如何使用它编写出功能丰富的程序
Hello Java DB内嵌模式的Derby
既然有了内嵌(embedded)的数据库就让我们从一个简单的范例(代码在 清单 中列出)开始试着使用它吧这个程序做了大多数数据库应用都可能会做的操作在 DBMS 中创建了一个名为 helloDB 的数据库创建了一张数据表取名为 hellotable向表内插入了两条数据然后查询数据并将结果打印在控制台上最后删除表和数据库释放资源
清单 HelloJavaDB 的代码
public class HelloJavaDB {
public static void main(String[] args) {
try { // load the driver
ClassforName(orgapachederbyjdbcEmbeddedDriver)newInstance();
Systemoutprintln(Load the embedded driver);
Connection conn = null;
Properties props = new Properties();
propsput(user user); propsput(password user);
//create and connect the database named helloDB
conn=DriverManagergetConnection(jdbc:derby:helloDB;create=true props);
Systemoutprintln(create and connect to helloDB);
connsetAutoCommit(false);
// create a table and insert two records
Statement s = conncreateStatement();
sexecute(create table hellotable(name varchar() score int));
Systemoutprintln(Created table hellotable);
sexecute(insert into hellotable values(Ruth Cao ));
sexecute(insert into hellotable values (Flora Shi ));
// list the two records
ResultSet rs = sexecuteQuery(
SELECT name score FROM hellotable ORDER BY score);
Systemoutprintln(name\t\tscore);
while(rsnext()) {
StringBuilder builder = new StringBuilder(rsgetString());
builderappend(\t);
builderappend(rsgetInt());
Systemoutprintln(buildertoString());
}
// delete the table
sexecute(drop table hellotable);
Systemoutprintln(Dropped table hellotable);
rsclose();
sclose();
Systemoutprintln(Closed result set and statement);
mit();
connclose();
Systemoutprintln(Committed transaction and closed connection);
try { // perform a clean shutdown
DriverManagergetConnection(jdbc:derby:;shutdown=true);
} catch (SQLException se) {
Systemoutprintln(Database shut down normally);
}
} catch (Throwable e) {
// handle the exception
}
Systemoutprintln(SimpleApp finished);
}
}
随后我们在命令行(本例为 Windows 平台当然其它系统下稍作改动即可)下键入以下命令
清单 运行 HelloJavaDB 命令
java –cp ;%JAVA_HOME%\db\lib\derbyjar HelloJavaDB
程序将会按照我们预想的那样执行图 是执行结果的一部分截屏
图 HelloJavaDB 程序的执行结果
上述的程序和以往没什么区别不同的是我们不需要再为 DBMS 的配置而劳神因为 Derby 已经自动地在当前目录下新建了一个名为 helloDB 的目录来物理地存储数据和日志需要做的只是注意命名问题在内嵌模式下驱动的名字应为 orgapachederbyjdbcEmbeddedDriver创建一个新数据库时需要在协议后加入 create=true另外关闭所有数据库以及 Derby 的引擎可以使用以下代码
清单 关闭所有数据库及 Derby 引擎
DriverManagergetConnection(jdbc:derby:;shutdown=true);
如果只想关闭一个数据库那么则可以调用
清单 关闭一个数据库
DriverManagergetConnection(jdbc:derby:helloDB;shutdown=true );
这样使用嵌入模式的 Derby 维护和管理数据库的成本接近于 这对于希望专心写代码的人来说不失为一个好消息然而有人不禁要问既然有了内嵌模式为什么大多数的 DBMS 都没有采取这样的模式呢?不妨做一个小实验当我们同时在两个命令行窗口下运行 HelloJavaDB 程序结果一个的结果与刚才一致而另一个却出现了错误如 图 所示
图 内嵌模式的局限
错误的原因其实很简单在使用内嵌模式时Derby 本身并不会在一个独立的进程中而是和应用程序一起在同一个 Java 虚拟机(JVM)里运行因此Derby 如同应用所使用的其它 jar 文件一样变成了应用的一部分这就不难理解为什么在 classpath 中加入 derby 的 jar 文件我们的示例程序就能够顺利运行了这也说明了只有一个 JVM 能够启动数据库而两个跑在不同 JVM 实例里的应用自然就不能够访问同一个数据库了
鑒于上述的局限性和来自不同 JVM 的多个连接想访问一个数据库的需求下一节将介绍 Derby 的另一种模式网络服务器(Network Server)
网络服务器模式
如上所述网络服务器模式是一种更为传统的客户端/服务器模式我们需要启动一个 Derby 的网络服务器用于处理客户端的请求不论这些请求是来自同一个 JVM 实例还是来自于网络上的另一台机器同时客户端使用 DRDA(Distributed Relational Database Architecture)协议连接到服务器端这是一个由 The Open Group 倡导的数据库交互标准图 说明了该模式的大体结构
由于 Derby 的开发者们努力使得网络服务器模式与内嵌模式之间的差异变小使得我们只需简单地修改 清单 中的程序就可以实现如 清单 所示我们在 HelloJavaDB 中增添了一个新的函数和一些字符串变量不难看出新的代码只是将一些在 上一节中特别指出的字符串进行了更改驱动类为 orgapachederbyjdbcClientDriver而连接数据库的协议则变成了 jdbc:derby://localhost:/这是一个类似 URL 的字符串而事实上Derby 网络的客户端的连接格式为jdbc:derby://server[:port]/databaseName[;attributeKey=value]在这个例子中我们使用了最简单的本地机器作为服务器而端口则是 Derby 默认的 端口
图 Derby 网络服务器模式架构
清单 网络服务器模式下的 HelloJavaDB
public class HelloJavaDB {
public static String driver = orgapachederbyjdbcEmbeddedDriver;
public static String protocol = jdbc:derby:;
public static void main(String[] args) {
// same as before
}
private static void parseArguments(String[] args) {
if (argslength == || argslength > ) {
return;
}
if (args[]equalsIgnoreCase(derbyclient)) {
framework = derbyclient;
driver = orgapachederbyjdbcClientDriver;
protocol = jdbc:derby://localhost:/;
}
}
}
当然仅仅有客户端是不够的我们还需要启动网络服务器Derby 中控制网络服务器的类是 orgapachederbydrdaNetworkServerControl因此键入以下命令即可如果想了解 NetworkServerControl 更多的选项只要把 start 参数去掉就可以看到帮助信息了关于网络服务器端的实现都被 Derby 包含在 derbynetjar 里
清单 启动网络服务器
java cp ;C:\Program Files\Java\jdk\db\lib\derbyjar;
C:\Program Files\Java\jdk\db\lib\derbynetjar
orgapachederbydrdaNetworkServerControl start
相对应的网络客户端的实现被包含在 derbyclientjar 中所以只需要在 classpath 中加入该 jar 文件修改后的客户端就可以顺利地读取数据了再一次尝试着使用两个命令行窗口去连接数据库就能够得到正确的结果了如果不再需要服务器那么使用 NetworkServerControl 的 shutdown 参数就能够关闭服务器
更多
至此文章介绍了 Java SE 中的新成员Java DB(Derby)也介绍了如何在内嵌模式以及网络服务器模式下使用 Java DB当然这只是浅尝辄止更多高级的选项还需要在 Sun 和 Derby 的文档中寻找在这一章的最后我们将简单介绍几个 Java DB 的小工具来加快开发速度它们都位于 orgapachederbytools 包内在开发过程中需要获取信息或者测试可以用到
ij一个用来运行 SQL 脚本的工具
dblook为 Derby 数据库作模式提取(Schema extraction)生成 DDL 的工具
sysinfo显示系统以及 Derby 信息的工具类
JDBC 新功能新 API
如果说上一章介绍了 Java 中的一个新成员它本来就存在但是没有被加入进 JDK那么这一章我们将关注在 JDBC 中又增加了哪些新功能以及与之相对应的新 API
自动加载驱动
在 JDBC 之前编写 JDBC 程序都需要加上以下这句有点丑陋的代码
清单 注册 JDBC 驱动
ClassforName(orgapachederbyjdbcEmbeddedDriver)newInstance();
JavasqlDriverManager 的内部实现机制决定了这样代码的出现只有先通过 ClassforName 找到特定驱动的 class 文件DriverManagergetConnection 方法才能顺利地获得 Java 应用和数据库的连接这样的代码为编写程序增加了不必要的负担JDK 的开发者也意识到了这一点从 Java 开始应用程序不再需要显式地加载驱动程序了DriverManager 开始能够自动地承担这项任务作为试验我们可以将 清单 中的相关代码删除重新编译后在 JRE 下运行结果和原先的程序一样
好奇的读者也许会问DriverManager 为什么能够做到自动加载呢?这就要归功于一种被称为 Service Provider 的新机制熟悉 Java 安全编程的程序员可能对其已经是司空见惯而它现在又出现在 JDBC 模块中JDBC 的规范规定所有 JDBC 的驱动 jar 文件必须包含一个 javasqlDriver它位于 jar 文件的 METAINF/services 目录下这个文件里每一行便描述了一个对应的驱动类其实编写这个文件的方式和编写一个只有关键字(key)而没有值(value)的 properties 文件类似同样地#之后的文字被认为是注释有了这样的描述DriverManager 就可以从当前在 CLASSPATH 中的驱动文件中找到它应该去加载哪些类而如果我们在 CLASSPATH 里没有任何 JDBC 的驱动文件的情况下调用 清单 中的代码会输出一个 sunjdbcodbcJdbcOdbcDriver 类型的对象而仔细浏览 JDK 的目录这个类型正是在 %JAVA_HOME%/jre/lib/resourcesjar 的 METAINF/services 目录下的 javasqlDriver 文件中描述的也就是说这是 JDK 中默认的驱动而如果开发人员想使得自己的驱动也能够被 DriverManager 找到只需要将对应的 jar 文件加入到 CLASSPATH 中就可以了当然对于那些 JDBC 之前的驱动文件我们还是只能显式地去加载了
清单 罗列本地机器上的 JDBC 驱动
Enumeration<Driver> drivers = DriverManagergetDrivers();
while(drivershasMoreElements()) {
Systemoutprintln(driversnextElement());
}
RowId
熟悉 DBOracle 等大型 DBMS 的人一定不会对 ROWID 这个概念陌生它是数据表中一个隐藏的列是每一行独一无二的标识表明这一行的物理或者逻辑位置由于 ROWID 类型的广泛使用Java SE 中新增了 javasqlRowId 的数据类型允许 JDBC 程序能够访问 SQL 中的 ROWID 类型诚然不是所有的 DBMS 都支持 ROWID 类型即使支持不同的 ROWID 也会有不同的生命周期因此使用 DatabaseMetaDatagetRowIdLifetime 来判断类型的生命周期不失为一项良好的实践经验我们在 清单 的程序获得连接之后增加以下代码便可以了解 ROWID 类型的支持情况
清单 了解 ROWID 类型的支持情况
DatabaseMetaData meta = conngetMetaData();
Systemoutprintln(metagetRowIdLifetime());
Java SE 的 API 规范中javasqlRowIdLifetime 规定了 种不同的生命周期ROWID_UNSUPPORTEDROWID_VALID_FOREVERROWID_VALID_OTHERROWID_VALID_SESSION 和 ROWID_VALID_TRANSACTION从字面上不难理解它们表示了不支持 ROWIDROWID 永远有效等等具体的信息还可以参看相关的 JavaDoc读者可以尝试着连接 Derby 进行试验会发现运行结果是 ROWID_UNSUPPORTED 即 Derby 并不支持 ROWID
既然提供了新的数据类型那么一些相应的获取更新数据表内容的新 API 也在 Java 中被添加进来和其它已有的类型一样在得到 ResultSet 或者 CallableStatement 之后调用 get/set/update 方法得到/设置/更新 RowId 对象示例的代码如 清单 所示
清单 获得/设置 RowId 对象
// Initialize a PreparedStatement
PreparedStatement pstmt = connectionprepareStatement(
SELECT rowid name score FROM hellotable WHERE rowid = ?);
// Bind rowid into prepared statement
pstmtsetRowId( rowid);
// Execute the statement
ResultSet rset = pstmtexecuteQuery();
// List the records
while(rsnext()) {
RowId id = rsgetRowId(); // get the immutable rowid object
String name = rsgetString();
int score = rsgetInt();
}
鑒于不同 DBMS 的不同实现RowID 对象通常在不同的数据源(datasource)之间并不是可移植的因此 JDBC 的 API 规范并不建议从连接 A 取出一个 RowID 对象将它用在连接 B 中以避免不同系统的差异而带来的难以解释的错误而至于像 Derby 这样不支持 RowId 的 DBMS程序将直接在 setRowId 方法处抛出 SQLFeatureNotSupportedException
SQLXML
SQL 标准引入了 SQL/XML作为 SQL 标准的扩展SQL/XML 定义了 SQL 语言怎样和 XML 交互如何创建 XML 数据如何在 SQL 语句中嵌入 XQuery 表达式等等作为 JDBC 的一部分Java 增加了 javasqlSQLXML 的类型JDBC 应用程序可以利用该类型初始化读取存储 XML 数据javasqlConnectioncreateSQLXML 方法就可以创建一个空白的 SQLXML 对象当获得这个对象之后便可以利用 setStringsetBinaryStreamsetCharacterStream 或者 setResult 等方法来初始化所表示的 XML 数据以 setCharacterStream 为例清单 表示了一个 SQLXML 对象如何获取 javaioWriter 对象从外部的 XML 文件中逐行读取内容从而完成初始化
清单 利用 setCharacterStream 方法来初始化 SQLXML 对象
SQLXML xml = concreateSQLXML();
Writer writer = xmlsetCharacterStream();
BufferedReader reader = new BufferedReader(new FileReader(testxml));
String line= null;
while((line = readerreadLine() != null) {
writerwrite(line);
}
由于 SQLXML 对象有可能与各种外部的资源有联系并且在一个事务中一直持有这些资源为了防止应用程序耗尽资源Java 提供了 free 方法来释放其资源类似的设计在 javasqlArrayClob 中都有出现
至于如何使用 SQLXML 与数据库进行交互其方法与其它的类型都十分相似可以参照 RowId 一节 中的例子在 Java SE 的 API 规范中找到 SQLXML 中对应的 get/set/update 方法构建类似的程序此处不再赘述
SQLExcpetion 的增强
在 Java SE 之前有关 JDBC 的异常类型不超过 个这似乎已经不足以描述日渐复杂的数据库异常情况因此Java SE 的设计人员对以 javasqlSQLException 为根的异常体系作了大幅度的改进首先SQLException 新实现了 Iterable<Throwable> 接口清单 实现了 清单 程序的异常处理机制这样简洁地遍历了每一个 SQLException 和它潜在的原因(cause)
清单 SQLException 的 foreach loop
// Java code
catch (Throwable e) {
if (e instanceof SQLException) {
for(Throwable ex : (SQLException)e ){
Systemerrprintln(extoString());
}
}
}
此外图 表示了全部的 SQLException 异常体系除去原有的 SQLException 的子类Java 中新增的异常类被分为 种SQLReoverableExceptionSQLNonTransientExceptionSQLTransientException在 SQLNonTransientException 和 SQLTransientException 之下还有若干子类详细地区分了 JDBC 程序中可能出现的各种错误情况大多数子类都会有对应的标准 SQLState 值很好地将 SQL 标准和 Java 类库结合在一起
图 SQLException 异常体系
在众多的异常类中比较常见的有 SQLFeatureNotSupportedException用来表示 JDBC 驱动不支持某项 JDBC 的特性例如在 Derby 下运行 清单 中的程序就可以发现 Derby 的驱动并不支持 RowId 的特性另外值得一提的是SQLClientInfoException 直接继承自 SQLException表示当一些客户端的属性不能被设置在一个数据库连接时所发生的异常
小结更多新特性与展望
在本文中我们已经向读者介绍了 Java SE 中 JDBC 最重要的一些新特性它们包括嵌在 JDK 中的 Java DB (Derby)和 JDBC 的一部分当然还有很多本文还没有覆盖到的新特性比如增加了对 SQL 语言中 NCHARNVARCHARLONGNVARCHAR 和 NCLOB 类型的支持在数据库连接池的环境下为管理 Statement 对象提供更多灵活便利的方法等
此外在 Java SE 的 beta 版中曾经将 Annotation Query 的特性包含进来这项特性定义了一系列 Query 和 DataSet 接口程序员可以通过撰写一些 Annotation 来自定义查询并获得定制的数据集结果但是由于这一特性的参考实现最终不能满足 JDK 的质量需求Sun 公司忍痛割爱取消了在 Java SE 中发布其的计划我们有理由相信在以后的 JDK 版本中这一特性以及更多新的功能将被包含进来利用 Java 语言构建数据库的应用也会变得更为自然顺畅