Java SE 6包含了对Java数据库互连(JDBC)API的一些增强特性。这些增强特性的版本将定为JDBC version 4.0。JDBC新特性的目的是提供更简单的设计和更好的开发体验。本文将概述JDBC 4.0的增强特性和为企业级Java开发人员带来的好处。我们将通过一个用Apache Derby作为后台数据库的贷款处理应用范例来探究JDBC的新特性。
版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接作者:feichangcai;
feichangcai原文:
http://www.matrix.org.cn/resource/article/2006-11-19/Mustang+JDBC_c8c66f03-77c2-11db-bdce-bdc029e475a1.html关键字:Mustang;JDBC
Java SE 6.0Java SE 6.0版以兼容性、稳定性和品质作为设计目标。本版本中有不少值得关注的增强特性,特别是JMX、web services、脚本语言支持(采用Rhino脚本引擎JSR 223把JavaScript技术与Java源码进行集成)、数据库连接、支持annotations和安全部分。另外,在JDBC API中还有不少新的特性,包括RowId支持和新增的SQLException子类。
JDBC 4.0的特性得益于Mustang中的Java SE 服务提供商机制,Java开发人员再也不必用类似Class.forName()的代码注册JDBC驱动来明确加载JDBC。当调用DriverManager.getConnection()方法时,DriverManager类将自动设置合适的驱动程序。该特性向后兼容,因此无需对现有的JDBC代码作任何改动。
通过对Java应用程序访问数据库代码的简化,使得JDBC 4.0有更好的开发体验。JDBC 4.0同时也提供了工具类来改进数据源和连接对象的管理,也改进了JDBC驱动加载和卸载机制。
有了JDBC 4.0传承自Java SE 5.0 (Tiger)版对元数据的支持功能,Java开发人员可用Annotations明确指明SQL查询。基于标注的SQL查询允许我们通过在Java代码中使用Annotation关键字正确指明SQL查询字符串。这样,我们不必查看JDBC代码和他所调用的数据库两份不同的文件。例如,用一个名为getActiveLoans()方法在贷款处理数据库中获取一个活跃贷款清单,你可以添加@Query(sql="SELECT * FROM LoanApplicationDetails WHERE LoanStatus = 'A'")标注来修饰该方法。
并且,最终版的Java SE 6开发包(JDK 6)以及其相应的执行期环境(JRE 6)会捆绑一个基于Apache Derby的数据库。这使得Java开发人员无需下载、安装和配置一款单独的数据库产品就能探究JDBC的新特性。
JDBC 4.0中增加的主要特性包括:
1. JDBC驱动类的自动加载
2. 连接管理的增强
3. 对RowId SQL类型的支持
4. SQL的DataSet实现使用了Annotations
5. SQL异常处理的增强
6. 对SQL XML的支持
另外,对BLOB/CLOB 的改进支持以及对国际字符集的支持也是JDBC 4.0的特性。这些特性将在随后章节中详细讨论。
JDBC驱动自动加载在JDBC 4.0中,我们不必再使用Class.forName()方法明确加载JDBC驱动。当调用getConnection方法时,DriverManager会尝试从初始化时已经加载的JDBC驱动程序库中选择合适的驱动,以及他在当前应用的同一个类加载器中明确加载使用过的驱动。
DriverManager中的getConnection和getDrivers方法已作了改进,以支持Java SE 服务提供商机制(SPM)。根据SPM,所谓服务就是一组广为人知的接口和抽象类的集合,而服务提供商就是对某一服务的特定实现。SPM还指明了服务提供商的配置文件存放于META-INF/services目录下。JDBC 4.0的驱动程序库必须包含META-INF/services/java.sql.Driver文件。该文件包含对java.sql.Driver 实现的JDBC驱动文件名。例如,通过JDBC驱动连接Apache Derby数据库,META-INF/services/java.sql.Driver将含有以下路径:
org.apache.derby.jdbc.EmbeddedDriver
我们再来快速地看一下如何使用这一新特性加载一个JDBC驱动管理。以下显示的是我们用以加载JDBC驱动的典型范例代码。这里我们假设连接的是Apache Derby数据库,因为该数据库将在本文随后的范例应用中用到:
Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
Connection conn =
DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
但在JDBC 4.0中,我们不必写Class.forName()这一行,我们只需要调用getConnection()方法取得数据库连接。
请注意,仅在完全独立的模式下可使用该方法取得数据库的连接。如果你使用一些类似数据库连接池等技术管理连接,那么代码将有所不同。
连接管理在JDBC 4.0之前,我们依靠JDBC URL来定义一个数据源连接。现在有了JDBC 4.0,我们只需为标准连接工厂机制提供一组参数,就能获取与任何数据源的连接。Connection和Statement接口增添的新方法为池环境中管理Statement对象提供了更好的连接状态跟踪机制和更大的灵活度。元数据工具(JSR-175)被用来管理活跃连接。我们还能获得元数据信息,如:活跃连接状态,并能指明XA事务的某一连接是标准式(Connection,在独立应用的情况下)、池式(PooledConnection)还是分布式(XAConnection)。该接口仅在诸如WebLogic、WebSphere和JBoss等Java EE应用服务器的事务管理中使用。
RowId支持JDBC 4.0增加了RowID接口以支持ROWID数据类型,Oracle和DB2数据库支持该数据类型。在你需要把大量缺少唯一标识符字段的查询记录放入一个不允许重复对象的Collection容器(如Hashtable)的情况下,RowId很有用。我们可以用ResultSet的getRowId()方法获取RowId,用PreparedStatement的setRowId()方法在查询中使用RowId。
关于RowId对象需要记住的一件重要事项是,RowId值在数据源之间不可移植。当在PreparedStatement和ResultSet中单独使用set或update方法时,需要想到指明数据源。因此,RowId对象不应被不同的Connection和ResultSet对象共享。
DatabaseMetaData中的getRowIdLifetime()方法可被用来确定RowId对象的有效存活时间。该方法的可能返回值如表1所列:
RowId值 描述
ROWID_UNSUPPORTED Doesn't support ROWID data type.
ROWID_VALID_OTHER Lifetime of the RowID is dependent on database vendor implementation.
ROWID_VALID_TRANSACTION Lifetime of the RowID is within the current transaction as long as the row in the database table is not deleted.
ROWID_VALID_SESSION Lifetime of the RowID is the duration of the current session as long as the row in the database table is not deleted.
ROWID_VALID_FOREVER Lifetime of the RowID is unlimited as long as the row in the database table is not deleted.
基于标注的SQL查询JDBC 4.0对标注(Java SE 5新增)作了进一步规范和补充,他允许开发人员不必再写大量代码就能达到联系SQL查询和Java类的目的。并且,通过使用Generics(JSR 014)和元数据(JSR 175) API,我们只要指明查询的输入和输出参数就能将SQL查询与Java对象进行关联。我们还可以将查询结果捆绑在Java类上以加快查询输出的处理。将查询对象置于Java对象之中,我们可以不必像往常一样写所有代码。在Java代码中指明SQL查询,经常用到两种标注:
Select标注Select标注用于在Java类中指明一个选择查询,使get方法能从数据库表中取回数据。表2显示的是Select标注的不同属性及其作用:
变量 类型 描述
sql String SQL Select query string.
value String Same as sql attribute.
tableName String Name of the database table against which the sql will be invoked.
readOnly, connected, scrollable Boolean Flags used to indicate if the returned DataSet is read-only or updateable, is connected to the back-end database, and is scrollable when used in connected mode respectively.
allColumnsMapped Boolean Flag to indicate if the column names in the sql annotation element are mapped 1-to-1 with the fields in the DataSet.
这里有一个应用Select标注从贷款数据库中获取所有活跃贷款的例子:
interface LoanAppDetailsQuery extends BaseQuery {
@Select("SELECT * FROM LoanDetais where LoanStatus = 'A'")
DataSet<LoanApplication> getAllActiveLoans();
}
Sql标注同样允许I/O参数(参数标记以一个问号后跟一个整型数据表示)。这里是一个参数化sql查询的例子:
interface LoanAppDetailsQuery extends BaseQuery {
@Select(sql="SELECT * from LoanDetails
where borrowerFirstName= ?1 and borrowerLastName= ?2")
DataSet<LoanApplication> getLoanDetailsByBorrowerName(String borrFirstName,
String borrLastName);
}
Update标注Update标注用于修饰Query接口方法以更新数据库表中的一条或多条记录。Update标注必须包含一个sql标注类型元素。这里是一个Update标注的例子:
interface LoanAppDetailsQuery extends BaseQuery {
@Update(sql="update LoanDetails set LoanStatus = ?1
where loanId = ?2")
boolean updateLoanStatus(String loanStatus, int loanId);
}
SQL异常处理的增强特性异常处理是Java编程的一个重要部分,特别是对后台关系数据库进行连接或查询时。SQLException是我们用以指出与数据库相关错误的类。JDBC 4.0在SQLException处理中有不少增强。为了在处理SQLException时获得更好的开发体验,JDBC 4.0版作了如下增强:
1. 新的SQLException子类
2. 对因果关系的支持
3. 对改进的for-each循环的支持
新的SQLException类SQLException的新子类提供了一种方法,使Java开发人员能写出更方便改动的错误处理代码。JDBC 4.0介绍了两个新的SQLException型别:
• SQL non-transient exception
• SQL transient exception
Non-Transient Exception:除非修正引发SQLException异常的代码,否则该异常在再次尝试相同JDBC操作失败后被抛出。表3显示了JDBC 4.0新增的SQLNonTransientException异常子类(SQL 2003规范中定义了SQLState类的值):
Exception class SQLState value
SQLFeatureNotSupportedException 0A
SQLNonTransientConnectionException 08
SQLDataException 22
SQLIntegrityConstraintViolationException 23
SQLInvalidAuthorizationException 28
SQLSyntaxErrorException 42
Transient Exception:当先前执行失败的JDBC操作在没有任何应用级功能干涉的情况下可能成功执行时,该异常被抛出。继承自SQLTransientException的新异常如表4所列:
Exception class SQLState value
SQLTransientConnectionException 08
SQLTransactionRollbackException 40
SQLTimeoutException None
因果关系SQLException类现在支持Java SE链式异常机制(又称因果工具),它使我们能在一个JDBC操作中处理多条SQLException异常(如果后台数据库支持多条异常特性)。这种情形发生在执行一条可能会抛出多条SQLException异常的语句时。
我们可以调用SQLException中的getNextException()方法在异常链中进行迭代。这里是一些处理getNextException()因果关系的范例代码:
catch(SQLException ex) {
while(ex != null) {
LOG.error("SQL State:" + ex.getSQLState());
LOG.error("Error Code:" + ex.getErrorCode());
LOG.error("Message:" + ex.getMessage());
Throwable t = ex.getCause();
while(t != null) {
LOG.error("Cause:" + t);
t = t.getCause();
}
ex = ex.getNextException();
}
}
增强的For-Each环Java SE 5中,SQLException类通过实现Iterable接口,增加了for-each循环支持的特性。这个循环的轨迹将会包括SQLException和其异常成因。这里的代码片断展示了SQLException中增加的增强型for-each环特性。
catch(SQLException ex) {
for(Throwable e : ex ) {
LOG.error("Error occurred: " + e);
}
}
对国际字符集转换的支持以下是JDBC类处理国际字符集的新增特性:
1. JDBC数据类型:新增NCHAR、NVARCHAR、LONGNVARCHAR和NCLOB数据类型。
2. PreparedStatement:新增setNString、setNCharacterStream和setNClob方法。
3. CallableStatement:新增getNClob、getNString和getNCharacterStream方法。
4. ResultSet:ResultSet接口新增updateNClob、updateNString和updateNCharacterStream方法。
对大对象(BLOBs and CLOBs)支持的改进以下是JDBC 4.0处理LOBs的新增特性:
1. Connection:新增方法(createBlob()、createClob()和createNClob())以创建BLOB、CLOB和NCLOB对象新实例。
2. PreparedStatement:新增方法setBlob()、setClob()和setNClob()以使用InputStream对象插入BLOB对象,使用Reader对象插入CLOB和NCLOB对象。
3. LOBs:在Blob、Clob和NClob接口中新增方法(free())以释放这些对象所持有的资源。
现在,让我们来看一看java.sql和javax.jdbc包的新类,以及他们所提供的服务。
JDBC 4.0 API:新类RowId (java.sql)正如先前所介绍的,该接口是对数据库中SQL ROWID值的展示。ROWID是一种SQL内建的数据类型,用来标识数据库中的一行特定数据。ROWID经常用于从表中返回查询结果,而这些结果行往往缺少唯一ID字段。
getRowId和setRowId等CallableStatement、PreparedStatement和ResultSet接口的方法允许程序员访问SQL ROWID值。RowId接口还提供了一个方法(叫getBytes())把ROWID值作为一个byte型数组返回。DatabaseMetaData接口有一个名为getRowIdLifetime的新方法,用以确定某一RowId对象的存活时间。RowId的存活时间范围可以是如下三种类型:
1. 创建RowId的数据库事务持续时间
2. 创建RowId的会话持续时间
3. 数据库对应的记录还未被删除的持续时间
DataSet (java.sql)DataSet接口提供了对执行SQL Query后所返回数据类型的安全检查。DataSet还可以运行在连接或非连接模式。在连接模式下,DataSet类似于ResultSet的功能;而在非连接模式下,他类似于CachedRowSet的功能。由于DataSet继承自List接口,因此我们能对查询返回的记录行进行迭代。
对已有的类,JDBC 4.0也新增了不少方法。比如,Connection新增了createSQLXML和createSQLXML方法,ResultSet新增了getRowId方法。
范例应用本文所示的范例应用是一个贷款处理应用软件,他有一个贷款搜索页面,用户可以通过输入贷款ID提交查询表,以获取贷款详情。贷款搜索页面调用一个控制器对象,而该对象又调用一个DAO对象访问后台数据库,以取回贷款详情。这些详情包括贷款人、贷款数额、贷款截至日期等信息,并显示在贷款详情屏幕上。后台数据库中,我们有一个名为LoanApplicationDetails的表,来存储贷款软件的详情。
该范例应用的用例是通过指定贷款ID来获取贷款详情。当贷款已经登记并将抵押物与利息挂钩后,贷款详情就可以被获取。表5显示了贷款处理应用软件项目的详情。
Name Value
Project Name JdbcApp
Project Directory c:\dev\projects\JdbcApp
DB Directory c:\dev\dbservers\apache\derby
JDK Directory c:\dev\java\jdk_1.6.0
IDE Directory c:\dev\tools\eclipse
Database Apache Derby 10.1.2.1
JDK 6.0 (beta 2 release)
IDE Eclipse 3.1
Unit Testing JUnit 4
Build Ant 1.6.5
下表所列的是我们连接贷款详情Apache Derby数据库所需的JDBC参数。这些参数存放于一个名为derby.properties的文本文件中,并置于项目的etc/jdbc目录下(见表6)。
Name Value
JDBC Driver File LoanApp\META-INF\services\java.sql.driver
Driver org.apache.derby.ClientDriver
URL jdbc:derby:derbyDB
User Id user1
Password user1
请注意:Apache Derby数据库提供了两种JDBC驱动:嵌入式驱动(org.apache.derby.jdbc.EmbeddedDriver)和客户端/服务器驱动(org.apache.derby.jdbc.ClientDriver)。我在范例应用中使用客户端/服务器版驱动。
以下是使用ij工具来启动Derby数据库服务器并创建新数据库的命令。
要启动Derby网络服务器,需开启一个命令行窗口,并运行如下命令(请根据你本机的环境改写DERBY_INSTALL和JAVA_HOME环境变量)。
set DERBY_INSTALL=C:\dev\dbservers\db-derby-10.1.2.1-bin
set JAVA_HOME=C:\dev\java\jdk1.6.0
set DERBY_INSTALL=C:\dev\dbservers\db-derby-10.1.3.1-bin
set CLASSPATH=%CLASSPATH%;%DERBY_INSTALL%\lib\derby.jar;
%DERBY_INSTALL%\lib\derbytools.jar;
%DERBY_INSTALL%\lib\derbynet.jar;
cd %DERBY_INSTALL%\frameworks\NetworkServer\bin
startNetworkServer.bat
要连接数据库服务器并创建测试数据库,需开启另一个命令行窗口并运行以下命令。请确保DERBY_INSTALL和JAVA_HOME环境变量符合你本机的环境。
set JAVA_HOME=C:\dev\java\jdk1.6.0
set DERBY_INSTALL=C:\dev\dbservers\db-derby-10.1.3.1-bin
set CLASSPATH=%DERBY_INSTALL%\lib\derbyclient.jar;
%DERBY_INSTALL%\lib\derbytools.jar;.
%JAVA_HOME%\bin\java org.apache.derby.tools.ij
connect 'jdbc:derby://localhost:1527/LoanDB;create=true';
测试要编译Java源代码,classpath需包括derby.jar和junit4.jar文件,这两个文件在项目的lib目录下。Classpath还需包括etc、etc/jdbc和etc/log4j目录,这样应用程序才能访问JDBC属性文件和Log4J配置文件。我创建了一个Ant构建脚本(在JdbcApp/build目录下)来自动完成编译和打包Java源代码的工作。
用于测试贷款详情数据库访问对象的测试类名为LoanAppDetailsDAOTest。我们传入贷款ID和贷款人参数就可以获取贷款详情。
以下部分显示了JDBC 4.0中自动加载JDBC驱动和基于标注的SQL查询特性的代码范例。
JDBC驱动的自动加载BaseDAO抽象类有一个名为getConnection的方法用以获得一个数据库连接。以下代码片断显示了该方法(注意,我们不必注册JDBC驱动)。只要java.sql.Driver文件中有合适的驱动程序类名(org.apache.derby.jdbc.ClientDriver),JDBC驱动将被自动加载。
protected Connection getConnection() throws DAOException {
// Load JDBC properties first
if (jdbcUrl == null || jdbcUser == null ||
jdbcPassword == null) {
loadJdbcProperties();
}
// Get Connection
Connection conn = null;
try {
conn = DriverManager.getConnection(jdbcUrl, jdbcUser,
jdbcPassword);
} catch (SQLException sqle) {
throw new DAOException("Error in getting a DB connection.",
sqle);
}
return conn;
}
SQL标注LoanAppDetailsQuery接口有标注了的SQL查询,用以获取活跃贷款清单(criteria is loanstatus="A")和某贷款人的贷款详情(在一个贷款人有多笔贷款的情况下)。在上文中,我们已经了解过了这些SQL标注。这里的范例代码显示了我们如何使用标注来调用已定义的SQL查询。
public DataSet<LoanAppDetails> getAllActiveLoans() throws Exception {
// Get Connection
Connection conn = getConnection();
LoanAppDetailsQuery query = null;
DataSet<LoanAppDetails> loanDetails = null;
query = QueryObjectFactory.createQueryObject(
LoanAppDetailsQuery.class, conn);
loanDetails = query.getAllActiveLoans();
return loanDetails;
}
public DataSet<LoanAppDetails> getLoanDetailsByBorrowerName(
String borrFirstName, String borrLastName) throws Exception {
// Get Connection
Connection conn = getConnection();
LoanAppDetailsQuery query = null;
DataSet<LoanAppDetails> loanDetails = null;
query = QueryObjectFactory.createQueryObject(
LoanAppDetailsQuery.class, conn);
loanDetails = query.getLoanDetailsByBorrowerName(
borrFirstName,borrLastName);
return loanDetails;
}
结论JDBC 4.0在SQL方面为开发者提供了更好的开发体验。JDBC 4.0的另一个目标是为API增加更丰富的工具以提供企业级JDBC特性管理JDBC资源。并且,JDBC 4.0 API还提供了JDBC驱动的移植方式,使其符合J2EE连接器架构(JCA)规范。这为JDBC厂商提供了向JDBC连接器迁移的能力。在面向企业服务的架构(SPA)中使用JDBC数据源,该移植方式很重要。在SOA中,JDBC数据源可被部署在另一个企业服务总线(ESB)架构内,而不需要为JDBC数据源另写一份ESB实现代码。
本文中,我们了解了RowId支持,JDBC驱动加载和基于标注的SQL等JDBC 4.0的增强特性。未来,JDBC 4.0还将增加更多特性以支持SQL:2003。请参阅特性文档,以获取更多有关JDBC 4.0特性的信息。