JDBC4.0的新特性(转载)
作者:feichangcai;feichangcai
原文:http://www.matrix.org.cn/resource/article/2006-11-19/Mustang+JDBC_c8c66f03-77c2-11db-bdce-bdc029e475a1.html
关键字:Mustang;JDBC
JavaSE6.0
JavaSE6.0版以兼容性、稳定性和品质作为设计目标。本版本中有不少值得关注的增强特性,特别是JMX、webservices、脚本语言支持(采用Rhino脚本引擎JSR223把JavaScript技术与Java源码进行集成)、数据库连接、支持annotations和安全部分。另外,在JDBCAPI中还有不少新的特性,包括RowId支持和新增的SQLException子类。
JDBC4.0的特性
得益于Mustang中的JavaSE服务提供商机制,Java开发人员再也不必用类似Class.forName()的代码注册JDBC驱动来明确加载JDBC。当调用DriverManager.getConnection()方法时,DriverManager类将自动设置合适的驱动程序。该特性向后兼容,因此无需对现有的JDBC代码作任何改动。
通过对Java应用程序访问数据库代码的简化,使得JDBC4.0有更好的开发体验。JDBC4.0同时也提供了工具类来改进数据源和连接对象的管理,也改进了JDBC驱动加载和卸载机制。
有了JDBC4.0传承自JavaSE5.0(Tiger)版对元数据的支持功能,Java开发人员可用Annotations明确指明SQL查询。基于标注的SQL查询允许我们通过在Java代码中使用Annotation关键字正确指明SQL查询字符串。这样,我们不必查看JDBC代码和他所调用的数据库两份不同的文件。例如,用一个名为getActiveLoans()方法在贷款处理数据库中获取一个活跃贷款清单,你可以添加@Query(sql="SELECT*FROMLoanApplicationDetailsWHERELoanStatus='A'")标注来修饰该方法。
并且,最终版的JavaSE6开发包(JDK6)以及其相应的执行期环境(JRE6)会捆绑一个基于ApacheDerby的数据库。这使得Java开发人员无需下载、安装和配置一款单独的数据库产品就能探究JDBC的新特性。
JDBC4.0中增加的主要特性包括:
1.JDBC驱动类的自动加载
2.连接管理的增强
3.对RowIdSQL类型的支持
4.SQL的DataSet实现使用了Annotations
5.SQL异常处理的增强
6.对SQLXML的支持
另外,对BLOB/CLOB的改进支持以及对国际字符集的支持也是JDBC4.0的特性。这些特性将在随后章节中详细讨论。
JDBC驱动自动加载
在JDBC4.0中,我们不必再使用Class.forName()方法明确加载JDBC驱动。当调用getConnection方法时,DriverManager会尝试从初始化时已经加载的JDBC驱动程序库中选择合适的驱动,以及他在当前应用的同一个类加载器中明确加载使用过的驱动。
DriverManager中的getConnection和getDrivers方法已作了改进,以支持JavaSE服务提供商机制(SPM)。根据SPM,所谓服务就是一组广为人知的接口和抽象类的集合,而服务提供商就是对某一服务的特定实现。SPM还指明了服务提供商的配置文件存放于META-INF/services目录下。JDBC4.0的驱动程序库必须包含META-INF/services/java.sql.Driver文件。该文件包含对java.sql.Driver实现的JDBC驱动文件名。例如,通过JDBC驱动连接ApacheDerby数据库,META-INF/services/java.sql.Driver将含有以下路径:
org.apache.derby.jdbc.EmbeddedDriver
我们再来快速地看一下如何使用这一新特性加载一个JDBC驱动管理。以下显示的是我们用以加载JDBC驱动的典型范例代码。这里我们假设连接的是ApacheDerby数据库,因为该数据库将在本文随后的范例应用中用到:
Class.forName("org.apache.derby.jdbc.EmbeddedDriver Connection conn =DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
但在JDBC4.0中,我们不必写Class.forName()这一行,我们只需要调用getConnection()方法取得数据库连接。
请注意,仅在完全独立的模式下可使用该方法取得数据库的连接。如果你使用一些类似数据库连接池等技术管理连接,那么代码将有所不同。
连接管理
在JDBC4.0之前,我们依靠JDBCURL来定义一个数据源连接。现在有了JDBC4.0,我们只需为标准连接工厂机制提供一组参数,就能获取与任何数据源的连接。Connection和Statement接口增添的新方法为池环境中管理Statement对象提供了更好的连接状态跟踪机制和更大的灵活度。元数据工具(JSR-175)被用来管理活跃连接。我们还能获得元数据信息,如:活跃连接状态,并能指明XA事务的某一连接是标准式(Connection,在独立应用的情况下)、池式(PooledConnection)还是分布式(XAConnection)。该接口仅在诸如WebLogic、WebSphere和JBoss等JavaEE应用服务器的事务管理中使用。
RowId支持
JDBC4.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_UNSUPPORTEDDoesn'tsupportROWIDdatatype.
ROWID_VALID_OTHERLifetimeoftheRowIDisdependentondatabasevendorimplementation.
ROWID_VALID_TRANSACTIONLifetimeoftheRowIDiswithinthecurrenttransactionaslongastherowinthedatabasetableisnotdeleted.
ROWID_VALID_SESSIONLifetimeoftheRowIDisthedurationofthecurrentsessionaslongastherowinthedatabasetableisnotdeleted.
ROWID_VALID_FOREVERLifetimeoftheRowIDisunlimitedaslongastherowinthedatabasetableisnotdeleted.
基于标注的SQL查询
JDBC4.0对标注(JavaSE5新增)作了进一步规范和补充,他允许开发人员不必再写大量代码就能达到联系SQL查询和Java类的目的。并且,通过使用Generics(JSR014)和元数据(JSR175)API,我们只要指明查询的输入和输出参数就能将SQL查询与Java对象进行关联。我们还可以将查询结果捆绑在Java类上以加快查询输出的处理。将查询对象置于Java对象之中,我们可以不必像往常一样写所有代码。在Java代码中指明SQL查询,经常用到两种标注:
Select标注
Select标注用于在Java类中指明一个选择查询,使get方法能从数据库表中取回数据。表2显示的是Select标注的不同属性及其作用:
变量类型描述
sqlStringSQLSelectquerystring.
valueStringSameassqlattribute.
tableNameStringNameofthedatabasetableagainstwhich-----------------------------thesqlwillbeinvoked.
readOnly,connected,scrollableBooleanFlagsusedtoindicateifthereturnedDataSetisread-onlyorupdateable,isconnectedtotheback-enddatabase,andisscrollablewhenusedinconnectedmoderespectively.
allColumnsMappedBooleanFlagtoindicateifthecolumnnamesinthesqlannotationelementaremapped1-to-1withthefieldsintheDataSet.
这里有一个应用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是我们用以指出与数据库相关错误的类。JDBC4.0在SQLException处理中有不少增强。为了在处理SQLException时获得更好的开发体验,JDBC4.0版作了如下增强:
1.新的SQLException子类
2.对因果关系的支持
3.对改进的for-each循环的支持
新的SQLException类
SQLException的新子类提供了一种方法,使Java开发人员能写出更方便改动的错误处理代码。JDBC4.0介绍了两个新的SQLException型别:
•SQLnon-transientexception
•SQLtransientexception
Non-TransientException:除非修正引发SQLException异常的代码,否则该异常在再次尝试相同JDBC操作失败后被抛出。表3显示了JDBC4.0新增的SQLNonTransientException异常子类(SQL2003规范中定义了SQLState类的值):
ExceptionclassSQLStatevalue
SQLFeatureNotSupportedException0A
SQLNonTransientConnectionException08
SQLDataException22
SQLIntegrityConstraintViolationException23
SQLInvalidAuthorizationException28
SQLSyntaxErrorException 42Transient Exception:当先前执行失败的JDBC操作在没有任何应用级功能干涉的情况下可能成功执行时,该异常被抛出。继承自SQLTransientException的新异常如表4所列:
ExceptionclassSQLStatevalue
SQLTransientConnectionException08
SQLTransactionRollbackException40
SQLTimeoutExceptionNone
因果关系
SQLException类现在支持JavaSE链式异常机制(又称因果工具),它使我们能在一个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环
JavaSE5中,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方法。
对大对象(BLOBsandCLOBs)支持的改进
以下是JDBC4.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包的新类,以及他们所提供的服务。
JDBC4.0API:新类
RowId(java.sql)
正如先前所介绍的,该接口是对数据库中SQLROWID值的展示。ROWID是一种SQL内建的数据类型,用来标识数据库中的一行特定数据。ROWID经常用于从表中返回查询结果,而这些结果行往往缺少唯一ID字段。
getRowId和setRowId等CallableStatement、PreparedStatement和ResultSet接口的方法允许程序员访问SQLROWID值。RowId接口还提供了一个方法(叫getBytes())把ROWID值作为一个byte型数组返回。DatabaseMetaData接口有一个名为getRowIdLifetime的新方法,用以确定某一RowId对象的存活时间。RowId的存活时间范围可以是如下三种类型:
1.创建RowId的数据库事务持续时间
2.创建RowId的会话持续时间
3.数据库对应的记录还未被删除的持续时间
DataSet(java.sql)
DataSet接口提供了对执行SQLQuery后所返回数据类型的安全检查。DataSet还可以运行在连接或非连接模式。在连接模式下,DataSet类似于ResultSet的功能;而在非连接模式下,他类似于CachedRowSet的功能。由于DataSet继承自List接口,因此我们能对查询返回的记录行进行迭代。
对已有的类,JDBC4.0也新增了不少方法。比如,Connection新增了createSQLXML和createSQLXML方法,ResultSet新增了getRowId方法。
范例应用
本文所示的范例应用是一个贷款处理应用软件,他有一个贷款搜索页面,用户可以通过输入贷款ID提交查询表,以获取贷款详情。贷款搜索页面调用一个控制器对象,而该对象又调用一个DAO对象访问后台数据库,以取回贷款详情。这些详情包括贷款人、贷款数额、贷款截至日期等信息,并显示在贷款详情屏幕上。后台数据库中,我们有一个名为LoanApplicationDetails的表,来存储贷款软件的详情。
该范例应用的用例是通过指定贷款ID来获取贷款详情。当贷款已经登记并将抵押物与利息挂钩后,贷款详情就可以被获取。表5显示了贷款处理应用软件项目的详情。
NameValue
ProjectNameJdbcApp
ProjectDirectoryc:\dev\projects\JdbcApp
DBDirectoryc:\dev\dbservers\apache\derby
JDKDirectoryc:\dev\java\jdk_1.6.0
IDEDirectoryc:\dev\tools\eclipse
DatabaseApacheDerby10.1.2.1
JDK6.0(beta2release)
IDEEclipse3.1
UnitTestingJUnit4
BuildAnt1.6.5
下表所列的是我们连接贷款详情ApacheDerby数据库所需的JDBC参数。这些参数存放于一个名为derby.properties的文本文件中,并置于项目的etc/jdbc目录下(见表6)。
NameValue
JDBCDriverFileLoanApp\META-INF\services\java.sql.driver
Driverorg.apache.derby.ClientDriver
URLjdbc:derby:derbyDB
UserIduser1
Passworduser1
请注意:ApacheDerby数据库提供了两种JDBC驱动:嵌入式驱动(org.apache.derby.jdbc.EmbeddedDriver)和客户端/服务器驱动(org.apache.derby.jdbc.ClientDriver)。我在范例应用中使用客户端/服务器版驱动。
以下是使用ij工具来启动Derby数据库服务器并创建新数据库的命令。
要启动Derby网络服务器,需开启一个命令行窗口,并运行如下命令(请根据你本机的环境改写DERBY_INSTALL和JAVA_HOME环境变量)。
setDERBY_INSTALL=C:\dev\dbservers\db-derby-10.1.2.1-binsetJAVA_HOME=C:\dev\java\jdk1.6.0setDERBY_INSTALL=C:\dev\dbservers\db-derby-10.1.3.1-binsetCLASSPATH=%CLASSPATH%;%DERBY_INSTALL%\lib\derby.jar;%DERBY_INSTALL%\lib\derbytools.jar;%DERBY_INSTALL%\lib\derbynet.jar;cd%DERBY_INSTALL%\frameworks\NetworkServer\binstartNetworkServer.bat
要连接数据库服务器并创建测试数据库,需开启另一个命令行窗口并运行以下命令。请确保DERBY_INSTALL和JAVA_HOME环境变量符合你本机的环境。
set JAVA_HOME=C:\dev\java\jdk1.6.0set DERBY_INSTALL=C:\dev\dbservers\db-derby-10.1.3.1-binset CLASSPATH=%DERBY_INSTALL%\lib\derbyclient.jar; %DERBY_INSTALL%\lib\derbytools.jar; .%JAVA_HOME%\bin\java org.apache.derby.tools.ijconnect '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和贷款人参数就可以获取贷款详情。
以下部分显示了JDBC4.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查询,用以获取活跃贷款清单(criteriaisloanstatus="A")和某贷款人的贷款详情(在一个贷款人有多笔贷款的情况下)。在上文中,我们已经了解过了这些SQL标注。这里的范例代码显示了我们如何使用标注来调用已定义的SQL查询。
结论
JDBC4.0在SQL方面为开发者提供了更好的开发体验。JDBC4.0的另一个目标是为API增加更丰富的工具以提供企业级JDBC特性管理JDBC资源。并且,JDBC4.0API还提供了JDBC驱动的移植方式,使其符合J2EE连接器架构(JCA)规范。这为JDBC厂商提供了向JDBC连接器迁移的能力。在面向企业服务的架构(SPA)中使用JDBC数据源,该移植方式很重要。在SOA中,JDBC数据源可被部署在另一个企业服务总线(ESB)架构内,而不需要为JDBC数据源另写一份ESB实现代码。