JDBC 4
注册JDBC驱动
在 JDBC 4.0 之前,编写 JDBC 程序都需要加上以下这句有点丑陋的代码:
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
Java.sql.DriverManager
的内部实现机制决定了这样代码的出现。
只有先通过Class.forName
找到特定驱动的 class 文件,DriverManager.getConnection
方法才能顺利地获得 Java 应用和数据库的连接。
这样的代码为编写程序增加了不必要的负担,JDK 的开发者也意识到了这一点。 从 Java 6 开始,应用程序不再需要显式地加载驱动程序了, 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 的驱动文件的情况下,
调用以下代码会输出一个sun.jdbc.odbc.JdbcOdbcDriver
类型的对象。
// 罗列本地机器上的 JDBC 驱动 Enumeration<Driver> drivers = DriverManager.getDrivers(); while(drivers.hasMoreElements()) { System.out.println(drivers.nextElement()); }
而仔细浏览 JDK 6 的目录,这个类型正是在$JAVA_HOME/jre/lib/resources.jar
的
META-INF/services
目录下的java.sql.Driver
文件中描述的。也就是说,
这是 JDK 中默认的驱动。而如果开发人员想使得自己的驱动也能够被 DriverManager 找到,
只需要将对应的 jar 文件加入到CLASSPATH
中就可以了。
当然,对于那些 JDBC 4.0 之前的驱动文件,我们还是只能显式地去加载了。
RowId
熟悉 DB2、Oracle 等大型 DBMS 的人一定不会对 ROWID 这个概念陌生:
它是数据表中一个「隐藏」的列,是每一行独一无二的标识,表明这一行的物理或者逻辑位置。
由于ROWID
类型的广泛使用,Java SE 6 中新增了java.sql.RowId
的数据类型,
允许 JDBC 程序能够访问 SQL 中的 ROWID 类型。
诚然,不是所有的 DBMS 都支持 ROWID 类型。即使支持,不同的 ROWID 也会有不同的生命周期。
因此使用DatabaseMetaData.getRowIdLifetime
来判断类型的生命周期不失为一项良好的实践经验。
我们在下代码可以了解 ROWID 类型的支持情况:
// 了解 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 对象,示例的代码:
// 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
为例,以下代码表示了一个SQLXML
对象如何获取java.io.Writer
对象,
从外部的 XML 文件中逐行读取内容,从而完成初始化。
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>
接口。
除去原有的SQLException
的子类,Java 6 中新增的异常类被分为 3 种:
- SQLReoverableException
- SQLNonTransientException
- SQLTransientException
在SQLNonTransientException
和SQLTransientException
之下还有若干子类,
详细地区分了 JDBC 程序中可能出现的各种错误情况。大多数子类都会有对应的标准SQLState
值,
很好地将 SQL 标准和 Java 6 类库结合在一起。
在众多的异常类中,比较常见的有SQLFeatureNotSupportedException
,
用来表示 JDBC 驱动不支持某项 JDBC 的特性。例如在 Derby 下并不支持 RowId 的特性。
另外值得一提的是,SQLClientInfoException
直接继承自SQLException
,
表示当一些客户端的属性不能被设置在一个数据库连接时所发生的异常。
以下例子演示了异常处理机制。这样简洁地遍历了每一个SQLException
和它潜在的原因(cause)。
catch (Throwable e) { if (e instanceof SQLException) { for (Throwable ex : (SQLException) e) { System.err.println(ex.toString()); } } }
其他特性
其他还有增加了对 SQL 语言中NCHAR
、NVARCHAR
、LONGNVARCHAR
和NCLOB
类型的支持;
在数据库连接池的环境下为管理Statement
对象提供更多灵活、便利的方法等。