开篇:润墨网以专业的文秘视角,为您筛选了一篇JDBC4.0改进软件设计和性能范文,如需获取更多写作素材,在线客服老师一对一协助。欢迎您的阅读与分享!
Java数据库连接(JDBC)的最新版――4.0版,将成为Java标准版6.0的一部分。那么如何利用这项新的规范,进而改进Java应用中数据库访问及交互的设计和性能呢?本文讨论了JDBC 4.0具有的新特性,阐述了其解决一些现有问题的办法,并且通过示例介绍了设计和性能方面的改进。
Java数据库连接(JDBC)自第一个公开版本的核心Java语言问世以来就存在了,它在过去十年得到了长足发展。最新版本4.0将与Java标准版6.0(Java标准版是Sun公司为J2SE所取的新名字)装在同一个软件包,它体现了设计方面的重大改进,提供了更丰富的API,并且致力于开发的简易性以及生产力的提高。
看完本文后,读者朋友在开发下一组应用时应当可以利用这些新特性了。
注释和一般的DataSet
本文假定读者已经知道了注释(annotations)和泛型(generics),Java社区在推出J2SE 5.0的同时也引入了这两个概念。JDBC 4.0采用了注释和一般的DataSet。这种变化旨在简化执行SQL查询(在返回单一结果集的场景下)和SQL DML(数据操纵语言)语句(返回行数或者不返回任何值)。
新的API定义了一组Query和DataSet接口。Query接口定义了用JDBC注释标注的一组方法。这些标注方法描述了SQL选择和更新语句,并且指定了应如何实现结果集和DataSet的绑定。DataSet接口是一个参数化类型,由泛型来定义。DataSet接口为结果集数据提供了类型安全的定义。
所有Query接口从BaseQuery接口继承而来。使用Connection.createQueryObject()方法或者DataSource.createQueryObject()方法,并且把Query接口类型作为参数来传递,就可以为接口的具体实现创建实例。
DataSet接口则从java.util.List继承而来。描述结果集数据列的数据类就是它的参数类型,而数据类由Query接口的标有注释的方法返回。DataSet在连接和断开模式下都可以进行操纵及处理。因而,DataSet作为ResultSet或者CachedRowSet来实现,具体取决于操作模式:连接还是断开。作为java.util.List的子接口,DataSet允许通过java.util.Iterator接口,利用Iterator模式来访问数据行。
数据集或者用户自定义类是DataSet接口的参数类型,它可用两种方式来加以指定:作为一种结构,或者作为JavaBeans对象。每种方法都能达到这一目的:实现结果集数据列和用户自定义类定义的绑定,但JavaBeans组件模型比较简洁,便于在支持JavaBeans模型的其他框架里面重复使用对象定义。
例程1显示了简单示例的代码片段,表明如何利用新API来创建及运行SQL查询、使用用户自定义类来定义结果集数据,并且实现返回结果集和用户自定义规范的绑定。
例程1:员工用户自定义类型和employeeQueries
pubic class Employee {
private int employeeId;
private String firstName;
private String lastName;
public int getEmployeeId() {
return employeeId;
}
public setEmployeeId(int employeeId) {
this.employeeId = employeeId;
}
public String getFirstName() {
return firstName;
}
public setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public setLastName(String lastName) {
this.lastName = lastName;
}
}
interface EmployeeQueries extends BaseQuery {
@Select (sql="SELECT employeeId, firstName, lastName FROM employee")
DataSet< Employee > getAllEmployees ();
@Update (sql="delete from employee")
int deleteAllEmployees ();
}
Connection con = ...
EmployeeQueries empQueries = con.createQueryObject (EmployeeQueries.class);
DataSet< Employee > empData = empQueries.getAllEmployees ();
异常处理功能的改进
版本4.0以前的JDBC API具有的异常处理功能很有限,而且效果常常不够好。如果出现各种类型的错误,就会抛出SQLException。异常情况既没有进行分类,也没有定义它们的层次。要获得一些有意义的信息,惟一的办法就是检索及分析SQLState值。SQLState值及相应的意思随数据源的不同而不同。因此,查找问题根源及有效地处理异常情况就成了一项烦琐的任务。
JDBC 4.0改进了异常处理功能,缓解了部分上述问题。出现的主要变化如下: 把SQLException分成临时类型和非临时类型;
支持链接异常处理(chained exceptions);
实现了Iterable接口。
如果以前失败的操作检索成功,就会抛出SQLTransientException。如果检索不会带来成功操作、除非SQLException的原因得到纠正,就会抛出SQLNonTransientException。
如今JDBC 4.0包括了支持链接异常处理的功能。新的构造函数添加了额外参数,以查找出现异常的可能原因。多个SQLExceptions可在一次循环中反复执行;可调用 getCause()来查明异常的可能原因。如果非SQLException类型是导致异常的根本原因,getCause()方法可以返回它们。
SQLException类如今实现了Iterable接口,并为每个循环支持J2SE 5.0,以便循环执行起来更简单、更简洁。例程2描述了使用简化循环的新构造函数:
例程2:简化循环构造函数
catch(SQLException ex) {
for(Throwable t : ex) {
System.out.println("exception:" + t);
}
}
SQL/XML
如今,大量数据位于XML格式中。数据库通过在SQL 2003规范中定义标准XML类型,从而扩大了支持XML数据类型的功能。大多数数据库供应商在各自的新版本中实现了XML数据类型。由于加入了这种类型,XML数据集或者文档可以是数据库表格某一行中的其中一个字段或者列值。JDBC 4.0出现之前,要操纵JDBC框架里面的这类数据,最佳办法也许是使用驱动程序供应商提供的专有扩展件,或者作为CLOB类型来访问。
如今JDBC 4.0把SQLXML定义为映射数据库SQL XML类型的Java 数据类型。API支持把XML类型处理成字符串或者处理成StAX流。用于XML的流式API(StAX)基于Iterator模式,而用于XML的简单API(SAX)处理基于Observer模式。对Java而言,StAX通过JSR 173得到了采用。
调用Connection对象的createSQLXML()方法就可以创建SQLXML对象。这是一个空对象,所以只要使用setString()方法,或者使用createXMLStreamWriter()方法把XML流与对象关联起来,就可以为其附加数据。同样,使用getString()方法,或者使用createXMLStreamReader()把XML流与对象关联起来,就可以从SQLXML对象检索XML数据。
ResultSet、PreparedStatement和CallableStatement等接口拥有检索SQLXML数据类型的getSQLXML()方法。PreparedStatement和CallableStatement也拥有把SQLXML对象作为参数来添加的setSQLXML()方法。
SQLXML资源可通过调用free()方法来加以释放――如果对象需要在长时间运行的事务中保持有效性,这种方法可能很适合。也可以针对数据源调用DatabaseMetaData的getTypeInfo()方法,检查数据库是否支持SQLXML数据类型,因为这种方法可以返回它支持的所有数据类型。
连接和语句
Connection接口定义得到了改进,可分析连接状态及使用,从而便于提高效率。
有时候,数据库连接无法使用,尽管它们可能未必加以关闭、收集废料。在这种情况下,数据库显得速度缓慢、没有反应。在大多数这类情况下,重新初始化连接也许是解决问题的惟一办法。如果使用4.0版本以前的JDBC API,没有办法可以区别失效连接和关闭连接。新的API为Connection接口添加了isValid()方法,以查询连接是否仍然有效。
另外,数据库连接往往在客户端之间共享,而有时候有些客户端使用的资源往往多于另一些客户端,这会导致出现资源不足情形。Connection接口定义了setClientInfo()方法来定义客户端特定的属性,从而可以利用这些属性来分析及监控客户端使用资源的情况。
RowId
许多数据库里面的RowId 是标识表里面行的独特方法。在搜索标准中使用RowId的查询往往是最快的数据检索方法,如果是Oracle和DB2数据库,更是如此。因为java.sql.RowId现在是Java中的内建类型,可以利用与其用法相关的性能优点。不过值得一提的是,RowId往往只对某个表、而不是整个数据库而言具有惟一性。它们可能有变,不是得到所有数据库的支持。RowId通常不能在诸数据源之间进行移植,因而在处理多个数据源时,须小心使用。
RowId对数据源定义的生存期而言是有效的,而且前提是行没有被删除。调用DatabaseMetadata.getRowIdLifetime()方法即可确定RowId的生存期。返回类型是表中概述的枚举类型。
只要行没有被删除,ROWID_VALID_TRANSACTION、ROWID_VALID_SESSION和 ROWID_VALID_FOREVER定义都是真的。值得一提的是,如果某行被删除、重新插入――有时候这可能会在数据源以透明方式出现,新的 RowId就会被分配。譬如在Oracle数据库,如果对分区的表设置“enable row movement”子句,分区键的更新就会引起该行从一个分区移动到另一个分区,RowId就会变化。即使“alter table table_name”移动没有“enable row movement”标记,RowId也有可能变化。
ResultSet接口和CallableStatement接口都经过了更新,包括了名为getRowID()的方法,该方法可以返回javax.sql.RowId类型。例程3表明了如何从ResultSet和CallableStatement检索RowId:
例程3:获得RowId
//从ResultSet检索RowId的方法签名如下:
RowId getRowId (int columnIndex)
RowId getRowId (String columnName)
...
Statement stmt = con.createStatement ();
ResultSet rs = stmt. ExecuteQuery (…);
while (rs.next ()) {
...
java.sql.RowId rid = rs.getRowId (1);
...
}
//从CallableStatement检索RowId的方法签名如下:
RowId getRowId (int parameterIndex)
RowId getRowId (String parameterName)
Connection con;
...
CallableStatement cstmt = con.prepareCall (…);
...
cstmt.registerOutParameter (2, Types.ROWID);
...
cstmt.executeUpdate ();
...
java.sql.RowId rid = cstmt.getRowId (2);
RowId可用来以惟一方式查询行,因而可用来检索行或者更新行数据。使用RowId引用进行取用或者更新时,知道RowId的生命周期的有效性很重要,以确保结果的一致性。另外最好同时使用另一个引用,譬如主键,以避免RowId可能以透明方式变化的情况下出现不一致的结果。
RowId值也可以设置或者更新。如果是可以更新的ResultSet,updateRowId()方法可用来更新表中某一行的RowId。
PreparedStatement和CallableStatement接口都支持setRowId()方法把RowId设置成参数值,只是使用不同签名而已。该值可用来查询数据行,或者更新表中某一行的RowId值。
设置或者更新RowId的功能提供了可灵活控制惟一行标识符的优点,可用来让这种标识符对所用的表而言具有惟一性。只要显式设置一致的值,也有可能在支持的数据源之间移植RowId。然而,因为系统生成的RowId往往很有效,而且透明任务可以改变RowId,所以它们最好作为只读属性供应用使用。
利用供应商实现的非标准资源
新的JDBC API定义了java.sql.Wrapper接口。该接口提供了这项功能:通过使用相应的封装实例来检索委托实例(delegate instance),从而访问数据源供应商特定的资源。
按照目前这个规范,该Wrapper接口有17个子接口,其中包括Connection、ResultSet、Statement、CallableStatement、PreparedStatement、DataSource、DatabaseMetaData和ResultSetMetaData及其他子接口。这种设计非常好,因为它便于在查询创建和结果集检索生命周期的几乎所有阶段实现数据源供应商特定的资源。
unwrap()方法返回的对象可实现给定接口,以便访问供应商特定的方法。isWrapperFor()方法返回布尔值。如果它实现了接口,或者它直接或间接是该对象的封装器,就会返回真。
譬如说,使用Oracle时,Oracle JDBC驱动程序提供了功能增强的批量更新机制。与标准的JDBC批量更新机制相比,这种机制性能更好、效率更高。对较早期的JDBC版本而言,这意味着代码中使用Oracle特定的定义,譬如OraclePreparedStatement。这影响了代码的可移植性。如果使用新API,许多这类有效的实现就可以在标准的JDBC定义里面加以封装及。
用于加载驱动程序的服务提供者机制
在非管理或者独立的程序场景下,在JDBC 4.0出现以前,只有通过调用Class.forName方法,显式加载JDBC驱动程序类,如例程4所示:
例程4: Class.forName
Class.forName ("com.driverprovider.jdbc.jdbcDriverImpl");
若使用JDBC 4.0,如果JDBC驱动程序供应商把驱动程序封装成服务――按照JAR规范,这些服务根据服务提供者机制加以定义,那么DriverManager代码只要在类路径中搜索驱动程序,就可以显式加载它。这种机制的优点在于,使用JDBC时,开发人员用不着了解特定的驱动程序类,可以少编写代码。另外,因为代码里面再也没有驱动程序类名,名称变化后不需要重新编译。如果类路径指定了多个驱动程序,那么DriverManger就会使用在类路径中遇到的第一个驱动程序,竭力建立连接;需要的话,就会进一步反复进行。