Java
Platform, Standard Edition(Java SE)版本6(代码名称Mustang)现在已经推出了第二个beta版本,并计划于今年十月份交付使用。Java
SE 6包括几处对Java Database Connectivity (JDBC)API的增强。这些增强将被发布为JDBC 4.0版本。新JDBC功能的主要目标是提供更为简单的设计方式和更好的开发人员体验。本文概要说明了JDBC
4.0增强,以及它们给企业Java开发人员带来的好处。我们将借助一个使用Apache Derby作为后端数据库而构建的贷款处理示例应用程序,对新的JDBC功能进行探讨。
Java SE 6.0
Java SE 6.0版本的主要目标是提供兼容性、稳定性和高质量。这个版本中有几处有趣的增强,尤其是在监控与管理(JMX)、Web
service、脚本语言支持(使用Rhino脚本引擎JSR 223将JavaScript技术与Java源代码集成在一起)、数据库连接性、注释支持和安全性方面。JDBC
API中还增加了几个新功能,从新的RowId支持到更多的SQLException子类。
JDBC 4.0功能
借助Mustang中包含的Java SE Service Provider机制,Java开发人员不再需要使用像Class.forName()这样的代码显式地加载JDBC驱动程序,就能注册JDBC驱动程序。通过在调用DriverManager.getConnection()方法时自动定位合适的驱动程序,DriverManager类可以做到这一点。这个功能是向后兼容的,所以无需修改现有的JDBC代码。
在访问关系数据库的Java应用程序中,通过最小化我们需要编写的“模板”代码,JDBC 4.0还改善了开发人员体验。它还提供实用程序类,以改进JDBC驱动程序的注册和卸载机制,以及管理数据源和连接对象。
借助JDBC 4.0,Java开发人员现在可以使用Annotations指定SQL查询,从而利用Java
SE 5.0(Tiger)版本中提供的元数据支持。基于注释的SQL查询允许在Java代码中使用Annotation关键字指定SQL查询字符串。这样,我们就不必在两个不同文件中查看JDBC代码以及这些代码中调用的数据库查询了。例如,如果有一个叫做getActiveLoans()的方法,用于获取贷款处理数据库中的当前贷款,可以使用@Query(sql="SELECT
* FROM LoanApplicationDetails WHERE LoanStatus = 'A'")注释来修饰它。
此外,Java SE 6开发工具包(JDK 6)的最后版本——与运行时环境(JRE 6)相反——将会有一个基于与它绑定在一起的Apache
Derby的数据库。这将帮助开发人员理解新的JDBC功能,而不必单独下载、安装和配置数据库产品。
JDBC 4.0中加入的主要功能包括:
- 自动加载JDBC驱动程序类。
- 连接管理增强。
- 支持RowId SQL 类型。
- 使用Annotations的DataSet SQL实现。
- 处理增强的SQL异常。
- 支持SQL XML。
还存在其他功能,比如对大对象(BLOB/CLOB)的改进支持和National Character
Set Support。接下来的内容将会详细分析这些功能。
自动加载JDBC驱动程序
在JDBC 4.0中,调用getConnection方法时,不再需要使用Class.forName()显式地加载JDBC驱动程序,因为DriverManager将会试着从初始化时加载的以及使用与当前应用程序相同的类加载器显式加载的JDBC驱动程序中,找出合适的驱动程序来。
DriverManager方法getConnection和getDrivers已经增强为支持Java
SE Service Provider机制(SPM)。根据SPM,服务被定义为一组众所周知的接口和抽象类,而服务提供程序则是服务的特定实现。它还指定在META-INF/services目录中保存服务提供程序配置文件。JDBC
4.0驱动程序必须包含文件META-INF/services/java.sql.Driver。这个文件包含JDBC驱动程序的java.sql.Driver实现的名称。例如,要加载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)用于管理活动连接。我们还可以获得元数据信息,比如活动连接的状态,还可以把连接指定为标准的(Connection,用于单机应用程序)、池化的(PooledConnection)或者甚至是用于XA事务的分布式连接(XAConnection)。注意,我们没有直接使用XAConnection。它是由诸如WebLogic、WebSphere或JBoss这样的Java
EE应用服务器内部的事务管理器来使用的。
RowId支持
RowID接口被添加到JDBC 4.0中以支持ROWID数据类型,Oracle和DB2等数据库也支持这种数据类型。当有多条记录没有惟一标识符列,而且需要在不允许复制的Collection(比如Hashtable)中保存查询输出时,RowId很有用。我们可以使用ResultSet的getRowId()方法来获得RowId,并使用PreparedStatement的setRowId()方法在查询中使用RowId。
关于RowId对象要记住的一件重要事情是,分别在PreparedStatement和ResultSet中使用set或update方法时,RowId对象的值无法在数据源之间移植,可以认为它是特定于数据源的。所以,禁止在不同的Connection和ResultSet对象之间共享它。
DatabaseMetaData中的getRowIdLifetime()方法可用于确定RowId对象的生存期有效性。表1中列出了返回值或行id可能取的值。
RowId 值 |
描述 |
ROWID_UNSUPPORTED |
不支持ROWID数据类型。 |
ROWID_VALID_OTHER |
RowID的生存期依赖于数据库厂商实现。 |
ROWID_VALID_TRANSACTION |
只要在数据库表中行未被删除,RowID的生存期在当前的事务中。 |
ROWID_VALID_SESSION |
只要在数据库表中行未被删除,RowID的生存期在当前会话的持续时间中。 |
ROWID_VALID_FOREVER |
只要在数据库表中行未被删除,RowID的生存期是无限的。 |
基于注释的SQL查询
JDBC 4.0规范利用注释(Java SE 5中加入)允许开发人员把SQL查询与Java类关联在一起,同时不用编写大量的代码。此外,通过使用Generics(JSR
014)和元数据(JSR 175)API,我们可以把SQL查询与Java对象关联在一起,从而指定查询输入和输出参数。我们还可以把查询结果绑定到Java类,以加速对查询输出的处理。我们无需编写通常用于把查询结果填充到Java对象中的所有代码。在Java代码中指定SQL查询时,有2种主要的注释:Select和Update。
Select注释
Select注释用于在Java类中指定选择查询,以便使用get方法从数据库表中获取数据。表2显示了Select注释的各种属性以及它们的用法。
名称
|
类型
|
描述
|
sql |
String SQL |
Select查询字符串。 |
value |
String |
与sql属性相同。 |
tableName |
String |
在其上调用sql的数据库表的名称。 |
readOnly、connected、 scrollable |
Boolean |
标志,分别用于指示返回的DataSet是只读的还是可更新的,是否连接到后端数据库,在connected模式中使用时是否可以滚动。
|
allColumnsMapped |
Boolean |
标志,用于指示sql注释元素中的列名是否一对一地映射到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处理方面有几处增强。下面是JDBC 4.0版本中的一些增强,在处理SQLExceptions时它们可以为开发人员带来更好的体验:
- 新的SQLException子类
- 支持因果关系
- 支持增强的for-each循环
新的SQLException类
JDBC 4.0中创建了SQLException的新子类,以便为Java程序员提供一种编写更多可移植错误处理代码的手段。JDBC
4.0中引入了2类新的SQLException:
非瞬时异常:同一项操作重试失败时抛出此异常,直到SQLException的原因得到纠正为止。表3显示了JDBC
4.0中加入的新异常类,它们都是SQLNonTransientException的子类(SQLState类值定义在SQL 2003规范中。):
异常类 |
SQLState值 |
SQLFeatureNotSupportedException |
0A |
SQLNonTransientConnectionException |
08 |
SQLDataException |
22 |
SQLIntegrityConstraintViolationException |
23 |
SQLInvalidAuthorizationException |
28 |
SQLSyntaxErrorException |
42 |
瞬时异常:当操作在没有任何应用程序级功能进行干涉的情况下重试,前面失败的JDBC操作能够成功时抛出此异常。表4中列出了对SQLTransientException进行扩展的新异常。
异常类 |
SQLState值 |
SQLTransientConnectionException |
08 |
SQLTransactionRollbackException |
40 |
SQLTimeoutException |
None |
因果关系
现在,SQLException类支持配备有异常机制(也称为Cause工具)的Java SE,这种机制让我们能够处理JDBC操作中抛出的多种异常(如果后端数据库支持多异常功能)。这种场景发生在执行一条可能抛出多个SQLException的语句时。
我们可以使用SQLException中的getNextException()方法,通过异常链进行迭代。下面给出一些用于处理SQLException因果关系的示例代码:
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循环
SQLException类实现了Iterable接口,为Java SE 5中加入的for-each循环功能提供支持。循环的导航将遍历SQLException及其原因。下面给出一个代码片段,对SQLException中加入的for-each循环进行了说明。
catch(SQLException ex) {
for(Throwable e : ex ) {
LOG.error("Error occurred: " + e);
}
} |
对国家字符集转换的支持
下面列出了处理国家字符集(National Character Set)时JDBC类中所做的增强:
- JDBC数据类型:加入了新的JDBC数据类型,比如NCHAR、NVARCHAR、LONGNVARCHAR和NCLOB。
- PreparedStatement:加入了新方法setNString、setNCharacterStream和setNClob。
- CallableStatement:加入了新方法getNClob、getNString和getNCharacterStream。
- ResultSet:接口加入了新方法updateNClob、updateNString和updateNCharacterStream。
对大对象(BLOB和CLOB)的增强支持
下面列出了JDBC 4.0中对处理LOB所做的增强:
- Connection:加入了新方法(createBlob()、createClob()和createNClob())以创建BLOB、CLOB和NCLOB对象的新实例。
- PreparedStatement:加入了新方法setBlob()、setClob()和setNClob(),以便使用InputStream对象插入BLOB对象,以及使用Reader对象插入CLOB和NCLOB对象。
- LOB:Blob、Clob和NClob接口中加入了新方法(free()),以便释放这些对象占用的资源。
现在,让我们看一看java.sql和javax.jdbc包中加入的一些新类,以及它们提供了哪些服务。
JDBC 4.0 API:新类
RowId (java.sql)
正如前面提过的那样,这个接口代表着数据库中的一个SQL ROWID值。ROWID是一个内置的SQL数据类型,用于识别数据库表中的特定数据行。ROWID通常用在这样的查询中:该查询从输出行没有惟一ID列的表中返回行。
CallableStatement、PreparedStatement和ResultSet接口中的方法,比如getRowId和setRowId,允许程序员访问SQL
ROWID值。接口还提供一个方法(叫做getBytes())把ROWID的值返回为字节数组。DatabaseMetaData接口有一个叫做getRowIdLifetime的新方法,可用于确定RowId对象的生存期。RowId的作用域可以是下列3种类型之一:
- 在其中创建RowId的数据库事务的持续时间。
- 在其中创建RowId的会话的持续时间。
- 数据库表中的标识行,只要它尚未被删除。
DataSet (java.sql)
DataSet接口为从执行SQL查询返回的数据提供类型安全的视图。DataSet可以在已连接或未连接模式中进行操作。当在已连接模式中使用时,其功能类似于ResultSet。而在未连接模式中使用时,DataSet的功能则类似于CachedRowSet。因为DataSet扩展了List接口,我们可以遍历查询返回的行。
现有的类中还加入了几个新方法,比如Connection(createSQLXML、isValid)和ResultSet(getRowId)。
示例应用程序
本文中使用的示例应用程序是一个贷款处理应用程序,它包含一个贷款查找页面,用户可以在这个页面上输入一个贷款ID以获得有关贷款的详细信息,然后提交表单。贷款查找页面调用一个控制器对象,而此控制器对象又调用DAO对象来访问后端数据库,从而获得有关贷款的详细信息。这些详细信息包括借款人姓名、贷款金额和贷款到期时间,它们均会显示在一个贷款详细信息页面上。在后端数据库中,我们使用一个叫做LoanApplicationDetails的表来保存贷款应用程序的详细信息。
示例应用程序的用例是获得特定贷款ID的贷款详细信息。在注册贷款并针对抵押产品和利率锁定它之后,就可以获得这些贷款详细信息了。贷款处理应用程序的项目细节如表5所示。
名称 |
值 |
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)。
名称 |
值 |
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数据库提供2类JDBC驱动程序:Embedded Driver(org.apache.derby.jdbc.EmbeddedDriver)和Client/Server
Driver(org.apache.derby.jdbc.ClientDriver)。在示例应用程序中我使用的是Client/Server
Driver版本。 下面给出用于启动Derby数据库服务器和使用ij工具创建新数据库的命令。
要启动Derby Network Server,打开一个命令提示,然后运行以下命令(修改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设置应该包含位于项目主目录下lib目录中的derby.jar和junit4.jar文件。我们还需要在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查询,用于获得当前贷款(条件是loanstatus="A")的列表和基于贷款人姓名的贷款详细信息(在一个贷款人借贷多笔款项的情况下)。我们在本文的前面部分曾见过这些SQL注释。下面给出的示例代码说明了如何调用使用Annotation定义的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;
} |
结束语
在使用SQL时,JDBC 4.0可以提供开发的简便性并改善开发人员体验。JDBC 4.0的另一个目标是提供企业级的JDBC功能,把API公开给涵盖范围更广的工具集,以便更好地管理JDBC资源。此外,JDBC
4.0 API为JDBC驱动程序提供了一条迁移路径,从而与J2EE Connector架构(JCA)保持兼容。这使得JDBC厂商能够继续实现JDBC技术Connector
API。当在企业级面向服务架构(Service Oriented Architecture,SOA)应用程序中使用JDBC数据源时,这一点很重要,因为在企业级SOA应用程序中,可以把JDBC数据源部署为企业服务总线(Enterprise
Service Bus,ESB)架构中的另一个适配器,而不必为JDBC数据源编写ESB特定实现代码。
在本文中,我们讨论了JDBC 4.0中的增强,比如RowId支持、JDBC驱动程序加载和基于Annotations的SQL。JDBC
4.0中还将加入其他功能,以便在未来支持SQL 2003规范。要了解有关JDBC 4.0规范的更多信息,请参考规范文档。
参考资料
- 示例应用程序代码
- Java SE 6主页
- Java SE 6参考实现(Mustang)Web站点
- Java SE 6下载页面
- API规范
- Apache Derby主页
- Tomcat 5.5主页
|