Java SE 6中的JDBC 4.0增強
時間:2006-10-25 作者:Srini Penchikala 瀏覽次數:
5760 本文關鍵字:JDO, JDBC, SQLJ, Java, Srini Penchikala, jdbc, mustang, java se 6, RowID, driver, annotation, 驅動程序, 注釋 |
|
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規范的更多信息,請參考規范文檔。
參考資料