长久以来,因为大量(甚至几乎全部)的 Java 应用都依赖于数据库,如何使用 Java 语言高效、可靠、简洁地访问数据库一直是程序员们津津乐道的话题。新发布的 Java SE 6 也在这方面更上层楼,为编程人员提供了许多好用的新特性。其中最显著的,莫过于 Java SE 6 拥有了一个内嵌的 100% 用 Java 语言编写的数据库系统。而且,Java 6 开始支持 JDBC 4.0 的一系列新功能和属性。这样,Java SE 在对持久数据的访问上就显得更为易用和强大了。sql
Java DB:Java 6 里的数据库
新安装了 JDK 6 的程序员们也许会发现,除了传统的 bin、jre 等目录,JDK 6 新增了一个名为 db 的目录。这即是 Java 6 的新成员:Java DB。这是一个纯 Java 实现、开源的数据库管理系统(DBMS),源于 Apache 软件基金会(ASF)名下的项目 Derby。它只有 2MB 大小,对比动辄上 G 的数据库来讲可谓袖珍。但这并不妨碍 Derby 功能齐备,支持几乎大部分的数据库应用所须要的特性。更难能难得的是,依托于 ASF 强大的社区力量,Derby 获得了包括 IBM 和 Sun 等大公司以及全世界优秀程序员们的支持。这也难怪 Sun 公司会选择其 10.2.2 版本归入到 JDK 6 中,做为内嵌的数据库。这就好像为 JDK 注入了一股全新的活力:Java 程序员再也不须要耗费大量精力安装和配置数据库,就能进行安全、易用、标准、而且免费的数据库编程。在这一章中,咱们将初窥 Java DB 的世界,来探究如何使用它编写出功能丰富的程序。数据库
Hello, Java DB:内嵌模式的 Derby
既然有了内嵌(embedded)的数据库,就让咱们从一个简单的范例(代码在 清单 1 中列出)开始,试着使用它吧。这个程序作了大多数数据库应用均可能会作的操做:在 DBMS 中建立了一个名为 helloDB 的数据库;建立了一张数据表,取名为 hellotable;向表内插入了两条数据;而后,查询数据并将结果打印在控制台上;最后,删除表和数据库,释放资源。apache
清单 1. HelloJavaDB 的代码
public class HelloJavaDB { public static void main(String[] args) { try { // load the driver Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance(); System.out.println("Load the embedded driver"); Connection conn = null; Properties props = new Properties(); props.put("user", "user1"); props.put("password", "user1"); //create and connect the database named helloDB conn=DriverManager.getConnection("jdbc:derby:helloDB;create=true", props); System.out.println("create and connect to helloDB"); conn.setAutoCommit(false); // create a table and insert two records Statement s = conn.createStatement(); s.execute("create table hellotable(name varchar(40), score int)"); System.out.println("Created table hellotable"); s.execute("insert into hellotable values('Ruth Cao', 86)"); s.execute("insert into hellotable values ('Flora Shi', 92)"); // list the two records ResultSet rs = s.executeQuery( "SELECT name, score FROM hellotable ORDER BY score"); System.out.println("name\t\tscore"); while(rs.next()) { StringBuilder builder = new StringBuilder(rs.getString(1)); builder.append("\t"); builder.append(rs.getInt(2)); System.out.println(builder.toString()); } // delete the table s.execute("drop table hellotable"); System.out.println("Dropped table hellotable"); rs.close(); s.close(); System.out.println("Closed result set and statement"); conn.commit(); conn.close(); System.out.println("Committed transaction and closed connection"); try { // perform a clean shutdown DriverManager.getConnection("jdbc:derby:;shutdown=true"); } catch (SQLException se) { System.out.println("Database shut down normally"); } } catch (Throwable e) { // handle the exception } System.out.println("SimpleApp finished"); } }
随后,咱们在命令行(本例为 Windows 平台,固然,其它系统下稍做改动便可)下键入如下命令:编程
清单 2. 运行 HelloJavaDB 命令
java –cp .;%JAVA_HOME%\db\lib\derby.jar HelloJavaDB
程序将会按照咱们预想的那样执行,图 1 是执行结果的一部分截屏:api
图 1. HelloJavaDB 程序的执行结果

上述的程序和以往没什么区别。不一样的是咱们不须要再为 DBMS 的配置而劳神,由于 Derby 已经自动地在当前目录下新建了一个名为 helloDB 的目录,来物理地存储数据和日志。须要作的只是注意命名问题:在内嵌模式下驱动的名字应为 org.apache.derby.jdbc.EmbeddedDriver
;建立一个新数据库时须要在协议后加入 create=true
。另外,关闭全部数据库以及 Derby 的引擎可使用如下代码:安全
清单 3. 关闭全部数据库及 Derby 引擎
DriverManager.getConnection("jdbc:derby:;shutdown=true");
若是只想关闭一个数据库,那么则能够调用:服务器
清单 4. 关闭一个数据库
DriverManager.getConnection("jdbc:derby:helloDB;shutdown=true ");
这样,使用嵌入模式的 Derby 维护和管理数据库的成本接近于 0。这对于但愿专心写代码的人来讲不失为一个好消息。然而有人不由要问:既然有了内嵌模式,为何大多数的 DBMS 都没有采起这样的模式呢?不妨作一个小实验。当咱们同时在两个命令行窗口下运行 HelloJavaDB 程序。结果一个的结果与刚才一致,而另外一个却出现了错误,如 图 2 所示。
图 2. 内嵌模式的局限

错误的缘由其实很简单:在使用内嵌模式时,Derby 自己并不会在一个独立的进程中,而是和应用程序一块儿在同一个 Java 虚拟机(JVM)里运行。所以,Derby 如同应用所使用的其它 jar 文件同样变成了应用的一部分。这就不难理解为何在 classpath 中加入 derby 的 jar 文件,咱们的示例程序就可以顺利运行了。这也说明了只有一个 JVM 可以启动数据库:而两个跑在不一样 JVM 实例里的应用天然就不可以访问同一个数据库了。
鉴于上述的局限性,和来自不一样 JVM 的多个链接想访问一个数据库的需求,下一节将介绍 Derby 的另外一种模式:网络服务器(Network Server)。
网络服务器模式
如上所述,网络服务器模式是一种更为传统的客户端/服务器模式。咱们须要启动一个 Derby 的网络服务器用于处理客户端的请求,不论这些请求是来自同一个 JVM 实例,仍是来自于网络上的另外一台机器。同时,客户端使用 DRDA(Distributed Relational Database Architecture)协议链接到服务器端。这是一个由 The Open Group 倡导的数据库交互标准。图 3 说明了该模式的大致结构。
因为 Derby 的开发者们努力使得网络服务器模式与内嵌模式之间的差别变小,使得咱们只需简单地修改 清单 1 中的程序就能够实现。如 清单 5所示,咱们在 HelloJavaDB 中增添了一个新的函数和一些字符串变量。不难看出,新的代码只是将一些在 上一节中特别指出的字符串进行了更改:驱动类为 org.apache.derby.jdbc.ClientDriver
,而链接数据库的协议则变成了 jdbc:derby://localhost:1527/
。这是一个相似 URL 的字符串,而事实上,Derby 网络的客户端的链接格式为:jdbc:derby://server[:port]/databaseName[;attributeKey=value]
。在这个例子中,咱们使用了最简单的本地机器做为服务器,而端口则是 Derby 默认的 1527 端口。
图 3. Derby 网络服务器模式架构

清单 5. 网络服务器模式下的 HelloJavaDB
public class HelloJavaDB { public static String driver = "org.apache.derby.jdbc.EmbeddedDriver"; public static String protocol = "jdbc:derby:"; public static void main(String[] args) { // same as before } private static void parseArguments(String[] args) { if (args.length == 0 || args.length > 1) { return; } if (args[0].equalsIgnoreCase("derbyclient")) { framework = "derbyclient"; driver = "org.apache.derby.jdbc.ClientDriver"; protocol = "jdbc:derby://localhost:1527/"; } } }
固然,仅仅有客户端是不够的,咱们还须要启动网络服务器。Derby 中控制网络服务器的类是org.apache.derby.drda.NetworkServerControl
,所以键入如下命令便可。若是想了解 NetworkServerControl 更多的选项,只要把 start
参数去掉就能够看到帮助信息了。关于网络服务器端的实现,都被 Derby 包含在 derbynet.jar 里。
清单 6. 启动网络服务器
java -cp .;"C:\Program Files\Java\jdk1.6.0\db\lib\derby.jar"; "C:\Program Files\Java\jdk1.6.0\db\lib\derbynet.jar" org.apache.derby.drda.NetworkServerControl start
相对应的,网络客户端的实现被包含在 derbyclient.jar 中。因此,只须要在 classpath 中加入该 jar 文件,修改后的客户端就能够顺利地读取数据了。再一次尝试着使用两个命令行窗口去链接数据库,就可以获得正确的结果了。若是再也不须要服务器,那么使用 NetworkServerControl 的 shutdown 参数就可以关闭服务器。
更多
至此,文章介绍了 Java SE 6 中的新成员:Java DB(Derby),也介绍了如何在内嵌模式以及网络服务器模式下使用 Java DB。固然这只是浅尝辄止,更多高级的选项还须要在 Sun 和 Derby 的文档中寻找。在这一章的最后,咱们将简单介绍几个 Java DB 的小工具来加快开发速度。它们都位于 org.apache.derby.tools 包内,在开发过程当中须要获取信息或者测试能够用到。
- ij:一个用来运行 SQL 脚本的工具;
- dblook:为 Derby 数据库做模式提取(Schema extraction),生成 DDL 的工具;
- sysinfo:显示系统以及 Derby 信息的工具类;
JDBC 4.0:新功能,新 API
若是说上一章介绍了 Java 6 中的一个新成员,它原本就存在,可是没有被加入进 JDK。那么这一章,咱们将关注在 JDBC 4.0 中又增长了哪些新功能以及与之相对应的新 API。
自动加载驱动
在 JDBC 4.0 以前,编写 JDBC 程序都须要加上如下这句有点丑陋的代码:
清单 7. 注册 JDBC 驱动
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
Java.sql.DriverManager
的内部实现机制决定了这样代码的出现。只有先经过 Class.forName
找到特定驱动的 class 文件,DriverManager.getConnection
方法才能顺利地得到 Java 应用和数据库的链接。这样的代码为编写程序增长了没必要要的负担,JDK 的开发者也意识到了这一点。从 Java 6 开始,应用程序再也不须要显式地加载驱动程序了,DriverManager 开始可以自动地承担这项任务。做为试验,咱们能够将 清单 1 中的相关代码删除,从新编译后在 JRE 6.0 下运行,结果和原先的程序同样。
好奇的读者也许会问,DriverManager 为何可以作到自动加载呢?这就要归功于一种被称为 Service Provider 的新机制。熟悉 Java 安全编程的程序员可能对其已是司空见惯,而它如今又出如今 JDBC 模块中。JDBC 4.0 的规范规定,全部 JDBC 4.0 的驱动 jar 文件必须包含一个java.sql.Driver
,它位于 jar 文件的 META-INF/services 目录下。这个文件里每一行便描述了一个对应的驱动类。其实,编写这个文件的方式和编写一个只有关键字(key)而没有值(value)的 properties 文件相似。一样地,‘#’以后的文字被认为是注释。有了这样的描述,DriverManager 就能够从当前在 CLASSPATH 中的驱动文件中找到,它应该去加载哪些类。而若是咱们在 CLASSPATH 里没有任何 JDBC 4.0 的驱动文件的状况下,调用 清单 8 中的代码会输出一个 sun.jdbc.odbc.JdbcOdbcDriver
类型的对象。而仔细浏览 JDK 6 的目录,这个类型正是在 %JAVA_HOME%/jre/lib/resources.jar
的 META-INF/services 目录下的 java.sql.Driver
文件中描述的。也就是说,这是 JDK 中默认的驱动。而若是开发人员想使得本身的驱动也可以被 DriverManager 找到,只须要将对应的 jar 文件加入到 CLASSPATH 中就能够了。固然,对于那些 JDBC 4.0 以前的驱动文件,咱们仍是只能显式地去加载了。
清单 8. 罗列本地机器上的 JDBC 驱动
Enumeration<Driver> drivers = DriverManager.getDrivers(); while(drivers.hasMoreElements()) { System.out.println(drivers.nextElement()); }
RowId
熟悉 DB二、Oracle 等大型 DBMS 的人必定不会对 ROWID 这个概念陌生:它是数据表中一个“隐藏”的列,是每一行独一无二的标识,代表这一行的物理或者逻辑位置。因为 ROWID 类型的普遍使用,Java SE 6 中新增了 java.sql.RowId
的数据类型,容许 JDBC 程序可以访问 SQL 中的 ROWID 类型。诚然,不是全部的 DBMS 都支持 ROWID 类型。即便支持,不一样的 ROWID 也会有不一样的生命周期。所以使用DatabaseMetaData.getRowIdLifetime
来判断类型的生命周期不失为一项良好的实践经验。咱们在 清单 1 的程序得到链接以后增长如下代码,即可以了解 ROWID 类型的支持状况。
清单 9. 了解 ROWID 类型的支持状况
DatabaseMetaData meta = conn.getMetaData(); System.out.println(meta.getRowIdLifetime());
Java SE 6 的 API 规范中,java.sql.RowIdLifetime
规定了 5 种不一样的生命周期:ROWID_UNSUPPORTED
、ROWID_VALID_FOREVER
、ROWID_VALID_OTHER
、ROWID_VALID_SESSION
和 ROWID_VALID_TRANSACTION
。从字面上不难理解它们表示了不支持 ROWID、ROWID 永远有效等等。具体的信息,还能够参看相关的 JavaDoc。读者能够尝试着链接 Derby 进行试验,会发现运行结果是 ROWID_UNSUPPORTED
,即 Derby 并不支持 ROWID。
既然提供了新的数据类型,那么一些相应的获取、更新数据表内容的新 API 也在 Java 6 中被添加进来。和其它已有的类型同样,在获得ResultSet
或者 CallableStatement
以后,调用 get/set/update 方法获得/设置/更新 RowId 对象,示例的代码如 清单 10 所示。
清单 10. 得到/设置 RowId 对象
// Initialize a PreparedStatement PreparedStatement pstmt = connection.prepareStatement( "SELECT rowid, name, score FROM hellotable WHERE rowid = ?"); // Bind rowid into prepared statement. pstmt.setRowId(1, rowid); // Execute the statement ResultSet rset = pstmt.executeQuery(); // List the records while(rs.next()) { RowId id = rs.getRowId(1); // get the immutable rowid object String name = rs.getString(2); int score = rs.getInt(3); }
鉴于不一样 DBMS 的不一样实现,RowID 对象一般在不一样的数据源(datasource)之间并非可移植的。所以 JDBC 4.0 的 API 规范并不建议从链接 A 取出一个 RowID 对象,将它用在链接 B 中,以免不一样系统的差别而带来的难以解释的错误。而至于像 Derby 这样不支持 RowId 的 DBMS,程序将直接在 setRowId 方法处抛出 SQLFeatureNotSupportedException
。
SQLXML
SQL:2003 标准引入了 SQL/XML,做为 SQL 标准的扩展。SQL/XML 定义了 SQL 语言怎样和 XML 交互:如何建立 XML 数据;如何在 SQL 语句中嵌入 XQuery 表达式等等。做为 JDBC 4.0 的一部分,Java 6 增长了 java.sql.SQLXML
的类型。JDBC 应用程序能够利用该类型初始化、读取、存储 XML 数据。java.sql.Connection.createSQLXML
方法就能够建立一个空白的 SQLXML 对象。当得到这个对象以后,即可以利用setString
、setBinaryStream
、setCharacterStream
或者 setResult
等方法来初始化所表示的 XML 数据。以 setCharacterStream
为例,清单 11 表示了一个 SQLXML 对象如何获取 java.io.Writer
对象,从外部的 XML 文件中逐行读取内容,从而完成初始化。
清单 11. 利用 setCharacterStream 方法来初始化 SQLXML 对象
SQLXML xml = con.createSQLXML(); Writer writer = xml.setCharacterStream(); BufferedReader reader = new BufferedReader(new FileReader("test.xml")); String line= null; while((line = reader.readLine() != null) { writer.write(line); }
因为 SQLXML 对象有可能与各类外部的资源有联系,而且在一个事务中一直持有这些资源。为了防止应用程序耗尽资源,Java 6 提供了 free 方法来释放其资源。相似的设计在 java.sql.Array
、Clob
中都有出现。
至于如何使用 SQLXML 与数据库进行交互,其方法与其它的类型都十分类似。能够参照 RowId 一节 中的例子在 Java SE 6 的 API 规范中找到 SQLXML 中对应的 get/set/update 方法构建相似的程序,此处再也不赘述。
SQLExcpetion 的加强
在 Java SE 6 以前,有关 JDBC 的异常类型不超过 10 个。这彷佛已经不足以描述日渐复杂的数据库异常状况。所以,Java SE 6 的设计人员对以 java.sql.SQLException
为根的异常体系做了大幅度的改进。首先,SQLException 新实现了 Iterable<Throwable>
接口。清单 12 实现了清单 1 程序的异常处理机制。这样简洁地遍历了每个 SQLException 和它潜在的缘由(cause)。
清单 12. SQLException 的 for-each loop
// Java 6 code catch (Throwable e) { if (e instanceof SQLException) { for(Throwable ex : (SQLException)e ){ System.err.println(ex.toString()); } } }
此外,图 4 表示了所有的 SQLException 异常体系。除去原有的 SQLException 的子类,Java 6 中新增的异常类被分为 3 种:SQLReoverableException
、SQLNonTransientException
、SQLTransientException
。在 SQLNonTransientException
和SQLTransientException
之下还有若干子类,详细地区分了 JDBC 程序中可能出现的各类错误状况。大多数子类都会有对应的标准 SQLState
值,很好地将 SQL 标准和 Java 6 类库结合在一块儿。
图 4. SQLException 异常体系

在众多的异常类中,比较常见的有 SQLFeatureNotSupportedException
,用来表示 JDBC 驱动不支持某项 JDBC 的特性。例如在 Derby 下运行 清单 10 中的程序,就能够发现 Derby 的驱动并不支持 RowId 的特性。另外值得一提的是,SQLClientInfoException
直接继承自 SQLException,表示当一些客户端的属性不能被设置在一个数据库链接时所发生的异常。
小结:更多新特性与展望
在本文中,咱们已经向读者介绍了 Java SE 6 中 JDBC 最重要的一些新特性:它们包括嵌在 JDK 中的 Java DB (Derby)和 JDBC 4.0 的一部分。固然,还有不少本文尚未覆盖到的新特性。好比增长了对 SQL 语言中 NCHAR
、NVARCHAR
、LONGNVARCHAR
和 NCLOB
类型的支持;在数据库链接池的环境下为管理 Statement
对象提供更多灵活、便利的方法等。
此外,在 Java SE 6 的 beta 版中,曾经将 Annotation Query 的特性包含进来。这项特性定义了一系列 Query 和 DataSet 接口,程序员能够经过撰写一些 Annotation 来自定义查询并得到定制的数据集结果。可是,因为这一特性的参考实现最终不能知足 JDK 的质量需求,Sun 公司忍痛割爱,取消了在 Java SE 6 中发布其的计划。咱们有理由相信,在之后的 JDK 版本中,这一特性以及更多新的功能将被包含进来,利用 Java 语言构建数据库的应用也会变得更为天然、顺畅。
参考资料
- 阅读 Java SE 6 新特性系列 文章的完整列表,了解 Java SE 6 其它重要的加强。
- Java SE 6 文档:Java SE 6 的规范文档,能够找到绝大部分新特性的官方说明。
- 参考 Sun 公司 Java SE 6 关于 JDBC 的 API 参考文档:java.sql 和 javax.sql。
- developerWorks Apache Derby 项目资源中心:更多关于 Apache Derby 项目的技术文章和教程 。
- Java DB at a Glance:关于 Java DB 的介绍。
- Apache Derby: Quick Start:Apache Derby 的快速入门手册。
- 参考 JDBC 4.0 API 规范。