字符編碼轉換:$utf8 = iconv('gb2312','utf-8',$str);
獲得字符的編碼形式:mb_detect_encoding;
JDBC 是一個常用於訪問關系數據庫的標准數據訪問協議。JDBC 的一個顯著優勢是其標准化的 API,為基於 Java 的數據訪問應用程序的可移植性奠定了基礎。JDBC 是標准 Java (J2SE) 和企業 Java (J2EE) 中一個不可或缺的部分,在 Java 早期階段就已推出。
JDBC 有許多優勢,使得它能夠在許多 J2SE 和 J2EE 應用程序中發揮重要作用。但它也有一些不足之處,使得我們不能稱心如意的使用它們。這些麻煩(有時候讓人厭惡)的 JDBC 特性催生出了許多公開的 JDBC 抽象框架(例如 SQLExecutor 和 apache Jakarta Commons DBUtil)以及更多得多的自主開發的 JDBC 應用程序框架。Spring 框架的 JDBC 抽象就是一個公開的 JDBC 抽象框架。
Spring 框架是一個在 apache 許可下發布的 Java/J2EE 應用程序框架,它支持 J2EE 應用程序中的多個層次。Spring 框架的一個突出特性是支持更易於維護和更強健的 JDBC 數據訪問。在本文中,您將了解到 Spring 框架 — 它可以和 Oracle TopLink 對象/關系映射工具結合使用 — 如何大大減少與編寫 JDBC 代碼相關的煩瑣工作和風險。使用 Spring 框架,開發人員編寫的 Oracle 數據庫訪問 JDBC 代碼可以更為簡潔、更不易出錯以及更加靈活。
正確關閉數據庫資源
JDBC 代碼中的一個常見錯誤是沒有正確關閉連接。這將導致數據庫資源的不合理分配。類似地,關閉結果集和語句也是有用並通常推薦的操作。為了確保即使在異常的運行條件下也能正確執行這些關閉操作,一般將采用代碼清單 1 中 finally 子句中的代碼。
代碼清單 17 演示了本文中第一次在基於 Spring 的代碼中使用 PreparedStatement,並顯示了對 SQLException 的另一種引用。正如代碼清單 16 的情況一樣,SQLException 主要用於引用 Spring 框架的 JdbcTemplate 類,後者將處理它並將任何異常作為非強制 Spring 異常提供。
代碼清單 16 和 17 演示了 Spring 的 RowCallbackHandler 和 PreparedStatementSetter 回調接口的用法。在這些代碼清單中使用匿名內部類實現了這些接口。雖然與前面的代碼清單中顯示的 JdbcTemplate 的更簡單的用法相比,開發人員編寫的內部類必須知道關於 ResultSet 和 PreparedStatement 以及它們的各個 API 的更多信息,但您仍然無需關心 SQLException 的處理;JdbcTemplate 將執行異常處理。
前面的基於 Spring 的代碼清單(例如代碼清單 3 和 6 中使用的 JdbcTemplate)甚至沒有提到 ResultSet、Statement、PreparedStatement 或 SQLException。這些高度抽象的方法對於不想關心 JDBC 的具體用法的開發人員特別有用。不過,這些極其方便的方法沒有代碼清單 16 和 17 所演示的內部類方法靈活。代碼清單 16 和 17 中顯示的更靈活的方法可以在需要時使用(只需稍微了解基本的 JDBC API)。在所有情況下,異常處理都由 Spring 異常層次結構來一致地執行,您不需要關心 SQLException。
其他好處
代碼清單 1
try {// JDBC Connection/Statement/Result Set} catch (SQLException sqlEx){ // Handle the exception}finally {try{ // Closing connection *should* close statement and result setif (stmt != null) stmt.close(); if (conn != null) conn.close(); } catch (SQLException sqlEx) {System.err.println("SQLException NOT handled"); } }
finally 子句通常被用來確保關閉數據庫連接和語句。但即使當開發人員的確用這種方法確保成功關閉連接,代碼也是冗長、膨脹和重復的。Spring 框架對連接處理和相關資源管理進行了抽象,開發人員不用直接處理上述事項,從而實現更一致的資源關閉並編寫更易於理解的代碼。
第一個 Spring 代碼示例
代碼清單 2 中的 JDBC 代碼可以用來查詢(大家都熟悉)的 scott/tiger 模式中的員工的酬金。正如之前所討論的那樣,在本示例中除了實際查詢數據庫的 SQL 代碼之外,還必需要有大量的“例行”代碼。
代碼清單 2
List commissions = new ArrayList(); Statement stmt = null;ResultSet rs = null; try{stmt = this.myConnection.createStatement(); rs = stmt.executeQuery("SELECT comm FROM emp"); while ( rs.next() ) {Integer commission = new Integer( rs.getInt("COMM") ); if ( rs.wasNull() ) {// By assigning the commission to null, this effectively// represents a null in the database as a Java null.System.out.println( "\tCommission seen as " + commission +" is really null");commission = null; }commissions.add( commission ); }}catch (SQLException sqlEx) // checked{System.err.println( "Message:" + sqlEx.getMessage() );System.err.println( "Error Code:" + sqlEx.getErrorCode() ); System.err.println( "SQL State:" + sqlEx.getSQLState() ) ;}finally{try {if ( rs != null ) { rs.close(); }if ( stmt != null ) { stmt.close(); } }catch (SQLException sqlEx) // checked {System.err.println( sqlEx.getMessage() ); }}
代碼清單 3 中為使用 Spring 框架的代碼,它提供了類似於代碼清單 2 的功能。
代碼清單 3
List commissions = new ArrayList(); try{JdbcTemplate jt = new JdbcTemplate(this.myDataSource); List commList = jt.queryForList( "SELECT comm FROM emp"); Iterator commIter = commList.iterator(); while ( commIter.hasNext() ) {Number comm = (Number) ((Map) commIter.next()).get("COMM"); if (comm != null)commissions.add( new Integer(comm.intValue()) );else commissions.add( null ); } }catch ( DataAccessException ex ) // unchecked exception{System.err.println( ex.getMessage() ); }
值得注意的是與直接使用 JDBC 相比,利用 Spring 框架可以少得多的代碼實現同樣的功能。如代碼清單 3 所示,您不需要編寫和維護管理資源(連接、語句、結果集)的代碼。甚至代碼清單 3 中的少量的異常處理代碼也不是絕對必需的,因為 DataAccessException 是一個非強制異常。因為 Number 類型用來返回獎金,因此不需要顯式調用 ResultSet 的 wasNull 方法。實際上,您甚至在代碼清單 3 中的任何地方都找不到 ResultSet 語法!
代碼清單 3 還說明了由 Spring 框架的 JDBC 支持所提供和使用的基礎類之一 — JdbcTemplate。我們將使用一個數據源來完成這個由 Spring 提供的類的實例化,然後在模板類上使用提供的 SQL 字符串調用它的被覆蓋的 queryForList 方法之一。queryForList 方法將返回一個包含 HashMap 的 ArrayList,其中該 ArrayList 中的每一個元素都是一個返回的數據行,一個特定數組陣列元素中的每一個 Map 條目都是該行中的一個列值。
JdbcTemplate 提供了許多被覆蓋的 queryForList 方法,它們可以用來查詢潛在的多行數據。這個非常有用的類還提供了諸如 queryForInt(返回單個整數)、queryForLong(返回單個 long 型整數)、query、update 之類的方法。要分辨這些不同的被覆蓋的方法,最容易的方式是閱讀與 Spring 框架一起提供的基於 Javadoc 的 API 文檔中的“方法詳情”部分。這些方法的不同點在於使用的語句的類型(例如 Statement 或 PreparedStatement)和支持的特性。JdbcTemplate 還提供了一些方法,與上面使用的方法相比,它們需要更多的 JDBC 知識,但它們提供了更好的靈活性。這些更靈活但需要更多 JDBC 知識的方法將在本文稍後進行說明。
JDBC 異常處理
返回到代碼清單 1,注意 java.sql.SQLException 是唯一一個顯式捕獲的異常。SQLException 中捕獲了與數據庫和 SQL 相關的各種異常情況。描述 SQLException 類的 Javadoc 注釋介紹了可以從 SQLException 的實例中獲得的基本信息。這些信息包括錯誤描述字符串 [getMessage()]、某個標准化 SQLState 異常 String [getSQLState()] 和供應商特有的整型錯誤碼 [getErrorCode()]。在代碼清單 1 中實現的簡單的異常處理中使用了所有這三種信息。
SQLException 是一種強制異常(直接擴展 java.lang.Exception)。Java 的強制異常曾經引起很大爭議,現在 Java 社區似乎正在取得共識:只有當在應用程序能夠處理異常時才應使用強制異常。如應用程序代碼不能以有意義的方式處理異常,則不應當強制處理該異常。因為 SQLException 是強制異常,所以應用程序代碼必須處理它,或者捕獲它並對其進行一些處理或顯式地將其拋出給調用代碼。
SQLException 的最後一點細微差別在於它是使用 SQL 的關系數據源所特有的異常。這使得不適合將它包含在真正可移植的數據訪問對象 (DAO) 中,後者應當獨立於數據信息庫類型和訪問語言。
Spring 框架對 SQLException 的處理是其在支持更容易的 JDBC 開發和維護方面最有用的特性之一。Spring 框架提供了完成 SQLException 抽象化的 JDBC 支持,並提供了一個對 DAO 友好的非檢查異常層次結構。
處理供應商特有的錯誤碼
如上所述,標准的 SQLException 提供了一個標准化的信息段 (SQLState) 和一個供應商特有的信息段 (ErrorCode)。正如大多數的數據庫和它們的 JDBC 驅動程序實現一樣,Oracle 數據庫和 JDBC 驅動程序通過供應商特有的錯誤碼所提供的關於問題的詳細信息要比通過 SQLException 的與供應商無關的 SQLState 組件所提供的信息多得多。
Oracle 數據庫及其 JDBC 驅動程序通過 Error Code 提供的更豐富得多的詳細信息的一個明顯的例子是 SQLState 代碼 42000 (通常這指示語法錯誤或訪問問題)。對於 Oracle JDBC 驅動程序的大量不同的 Oracle 錯誤碼,SQLException 都將返回相同的 SQLState 值 (42000)。對應 SQLState 的 42000 值的一些 Oracle 錯誤碼包括 900(“無效 SQL 語句”)、903(“無效表名”)、904(“無效標識符”)、911(“無效字符”)和 936(“缺少表達式”)。此外很明顯任何來源於 Oracle JDBC 驅動程序的錯誤(與來源於 Oracle 數據庫的錯誤相反)都沒有任何對應的 SQLState。正如這些例子所顯示那樣,Oracle 特有的錯誤碼所提供的關於錯誤情況的詳細信息要比與供應商無關的 SQLState 所提供的信息更豐富得多。
有時候區分來源於 Oracle 數據庫的錯誤和來源於 Oracle JDBC 驅動程序的錯誤非常重要。例如,903(“無效的表名”)錯誤碼對應 Oracle 數據庫錯誤碼 ORA-00903。相反,17003(“無效的列索引”)錯誤碼對應 Oracle JDBC 驅動程序錯誤碼 ORA-17003。這兩種類型的錯誤碼(數據庫和 JDBC 驅動程序)都是 Oracle 特有的(如 ORA- 前綴所指示的那樣)。因為沒有為來源於 Oracle JDBC 驅動程序的錯誤提供 SQLState 錯誤碼,因此必須使用 Oracle 特有的錯誤碼來區分由 JDBC 驅動程序導致的錯誤。
在下面的表 1 中列出了訪問 Oracle 數據庫的 JDBC 中的一些最常見的錯誤。“示例代碼和/或注釋”列中的示例顯示了導致這種錯誤的 SQL 語句類型或提供了關於表中的該行上顯示的特定錯誤的其他注釋。
表 1
錯誤標記 Oracle 錯誤 SQLState 示例代碼和/或注釋
基於語句:SELECT ename FROM emp 變種的 SQL 相關錯誤
“唯一性約束” 1 2300 例如主鍵違規
“資源忙且指定 NOWAIT 獲取資源”
54 61000 只有在指定了 NOWAIT 時才出現
“無效的 SQL 語句”
900 42000 ename FROM emp
“無效的表名”
903 42000 SELECT ename FROM
“無效的標識符”
904 42000 SELECT empname FROM emp
“無效的字符”
911 42000 SELECT ename FROM emp;
“缺少列”
917 42000 在 INSERT 語句中需要逗號來分隔列時遺漏逗號可能是一個原因。
“在期望的位置沒有找到 FROM 關鍵字”
923 42000 SELECT ename emp
“缺少表達式”
936 42000 SELECT FROM emp
“表或視圖不存在”
942 42000 SELECT ename FROM empp
“不能插入空值”
1400 23000 試圖向包含 NOT NULL 約束的列中插入空值
“值大於該列的指定精度”
1438 22003 試圖插入比列允許的精度更多的數字位數
“無效的數字”
1722 42000 試圖對字符執行數值函數
“完整性約束失敗”
2291 23000 試圖插入包含與現有主鍵不匹配的外鍵的行
“值太大,”
12899 72000 試圖插入超出列允許范圍的的值(例如過多的字符)
“Io 異常”
17002 無 來源於 Oracle JDBC 驅動程序的錯誤沒有對應的 SQLState (null)
“無效的列索引”
17003 無
“無效的列名”
17006 無
“數值溢出”
17026 無
本文末尾的在線資源部分包含了到一個網站的鏈接,該網站詳細介紹了用戶可能遇到的各種 Oracle 數據庫異常。Oracle JDBC 驅動程序錯誤碼可以在 Oracle JDBC 開發人員指南和參考的附錄 B 中找到,幾種常見的 Oracle 數據庫產生的錯誤碼可以在 Oracle 數據庫錯誤消息文檔中找到(沒有列出產品特有的 ORA 消息)。
Spring 框架既支持基於標准的 SQLState 又支持供應商特有的錯誤碼。與自主開發的數據訪問軟件相比,該框架對供應商特有的錯誤碼的支持利用了與特有數據庫的更松散的耦合來實現。Spring 框架引入了一個 XML 配置文件,利用它將在 JDBC 代碼中經常遇到的某些供應商特有的錯誤與 Spring 支持的異常類連接起來。Spring 提供的 sql-error-codes.XML 配置文件目前包含了代碼清單 4 中所示針對 Oracle 數據庫的配置。(在該文件中還涉及其他的數據庫供應商,但並包含在此代碼清單中。)
代碼清單 4
900,903,904,917,936,942,17006 17003 17002 1,1400,1722,2291 54
sql-error-codes.xml 中的 value 元素的主體中的整數由逗號分隔,它們對應於前面所述的供應商特有的錯誤碼的數字部分。在表 1 中列出了 "badSqlGrammarCodes" 類別中的許多數字碼。17006 代碼是一個 JDBC 驅動程序錯誤碼,它指示“無效的列名”。代碼清單 4 中的 property 元素標記的 name 屬性指示 Spring 框架使用哪種類型的異常來處理那些特定的錯誤碼。例如,917 (ORA-00917) 錯誤將導致 Spring 框架拋出一個非強制的 BadSqlGrammarException。因為該配置文件是 XML 格式的並且在代碼的外部,因此可以很容易地將其他代碼添加到該文件中,用於拋出最適合於特定供應商錯誤碼的基於 Spring 的 JDBC 異常。
出於各種原因,您可能希望拋出與數據庫的錯誤碼對應的特定異常。例如,您可能希望選擇處理 SQLException 正常拋出的情況,而不是處理所有的情況。因為在許多情況下,在運行時您無法對代碼作任何處理。通過為數據庫開發人員創建一個更細粒化的異常層次結構,以及通過提供特定數據庫錯誤和特定異常之間的一個松散耦合的連接,Spring 框架使您能夠更輕松地處理那些容易處理的異常,而選擇忽略不能合理處理的非強制異常。
Spring 為 JDBC 支持提供的特別方便的異常類之一是 BadSqlGrammarException。該異常類提供了一個名稱為 getSql() 的方法,該方法將返回在拋出異常時正被調用的 SQL 語句。因為該類可以識別 SQL 的特征(它不是一個通用的 DAO 類),因此它還通過 getSQLException() 方法提供了標准 SQLException 的一個句柄。
除了將其他的 Oracle 特有的錯誤碼添加到 sql-error-codes.XML 文件中以將它們映射至現有的由 Spring 提供的異常類之外,您還可以創建定制的異常處理類。然後可以編寫一個定制的 SQLExceptionTranslator 類來將 Oracle 錯誤碼與這些定制的異常處理類連接起來。
將 PreparedStatement 用於 Spring
代碼清單 3 中的 Spring 示例依靠 Spring 的 Statement 包裝來執行 SQL 語句。然而,通常推薦使用 PreparedStatement 而不是 Statement 來對數據庫執行 SQL 語句。Spring JdbcTemplate 類為許多方法提供了在 Statement 和 PreparedStatement 兩者上構建的相同的功能,這樣便於您按需選擇 JDBC 語句的底層類型。
Spring 的基於 Javadoc 的 API 文檔詳細說明了各個方法是使用 Statement 還是 PreparedStatement。您還可以根據是否和 SQL 字符串一起傳遞了 SQL 參數給方法來分辨 JdbcTemplate 使用了哪一種類型。如果只傳入了 SQL 字符串,那麼方法一般使用 Statement。如果方法接收了 SQL 字符串的參數以及 SQL 語句,那麼方法一般使用 PreparedStatement。下面的兩個代碼清單(代碼清單 5 和代碼清單 6)顯示了使用 PreparedStatement 的標准 JDBC 訪問和包裝 PreparedStatement 的基於 Spring 的訪問。
代碼清單 5
String updateStr ="UPDATE salgrade SET losal = ?, hisal = ?WHERE grade = ?";PreparedStatement stmt = null; try{stmt = this.myConnection.prepareStatement(updateStr); stmt.setInt(1,aLowSal);stmt.setInt(2,aHighSal); stmt.setInt(3,aSalGrade); updateStatus = stmt.execute();stmt.close();} // lots of catch and finally code typically follows here
代碼清單 6
String updateStr ="UPDATE salgrade SET losal = ?, hisal = ?WHERE grade = ?"; JdbcTemplate jt = new JdbcTemplate(this.myDataSource); jt.update( updateStr,new Object[] { new Integer(aLowSal), new Integer(aHighSal),new Integer(aSalGrade) } ); // No handling/closing of PreparedStatement or catch/finally needed
代碼清單 6 為使用 Spring 框架來更新數據庫的一個示例。雖然 "PreparedStatement" 語義沒有在代碼清單中出現,但本例中使用的 JdbcTemplate 的特殊的更新方法的確使用了 PreparedStatement。JDBC 的標准用法要求捕獲 SQLException,並且必需一個 finally 程序塊來確保關閉語句。代碼清單 6 中的基於 Spring 的代碼則不需要這些。
注意在代碼清單 6 中的可執行代碼中沒有顯式地提及 PreparedStatement。使用這個方便的 JdbcTemplate 更新方法的開發人員不需要關心 PreparedStatement 的具體用法、其 API 或 SQLException。注意開發人員使用了一個匿名內部類,以提供要和 SQL 字符串一起傳遞給 JdbcTemplate.update 方法的值。
使用 Oracle 特有的 SQL 語句
Spring 框架的一個有用的特點是它僅專注於“包裝”JDBC 開發的最常用和最麻煩的方面,而不會過度阻止在需要的時候使用專有的 SQL/JDBC。雖然我們都希望使我們的代碼完全標准化(如果這樣做對我們無任何影響或有些一定影響),但有很多時候使用特殊的供應商特有的特性將是謹慎甚至必須的做法。
在 Oracle 范疇中的一個示例就是使用 Oracle 的 ROWID 來唯一描述 Oracle 表中的行。代碼清單 7 和 8 顯示了傳統的基於 JDBC 和 Spring 的 JDBC 代碼,它們分別根據提供的員工號從 scott/tiger EMP 表中檢索 ROWID。在兩種情況下都提供了一個作為字符串返回的 ROWID。
代碼清單 7
String queryStr = "SELECT rowid FROM emp WHERE empno = "+ aEmpNum; // aEmpNum set previouslyString rowId = null; try{stmt = this.myConnection.createStatement(); rs = stmt.executeQuery(queryStr);while ( rs.next() ) {rowId = rs.getString("ROWID"); }} // lots of catch-finally code needed after this
代碼清單 8
String queryStr = "SELECT rowid FROM emp WHERE empno = "+ aEmpNum;String rowId = null; try{JdbcTemplate jt = new JdbcTemplate(this.myDataSource); Oracle.sql.ROWID oraRowId =(ROWID) jt.queryForObject(queryStr, ROWID.class); rowId = oraRowId.stringValue();} catch ( IncorrectResultSizeDataAccessException wrongSizeEx ) {// This unchecked exception is thrown in this case if more // than one result is returned from the query. // Explicitly printing out the results of this exception's // methods getExpectedSize() and getActualSize() is really not // necessary in this case because this exception's getMessage() // returns this same information in sentence form. System.err.println( wrongSizeEx.getMessage() ); System.err.print ( "Expected " + wrongSizeEx.getExpectedSize()+ " results, but actually got back "+ wrongSizeEx.getActualSize() + " results.");}
除了顯示 Spring 框架支持 Oracle 特有的關鍵字的靈活性之外,代碼清單 8 還顯示了 Spring 的 DAO 異常之一的用途。在代碼清單 8 中,如果不小心編輯了 queryStr 來返回所有的 ROWID,那麼將拋出 IncorrectResultSizeDataAccessException。
專有 Oracle SQL 的最為大家所熟悉的例子可能是無處不在的查詢 SELECT sysdate FROM dual。代碼清單 9 顯示了這個 Oracle 特有的查詢(不是 ANSI 標准的一部分)如何與 Spring 框架一起使用。
代碼清單 9
String queryStr = "SELECT sysdate FROM dual"; Date date = null; try{JdbcTemplate jt = new JdbcTemplate(this.myDataSource); date = (Date) jt.queryForObject(queryStr, Date.class);} catch ( BadSqlGrammarException badSqlEx ) // unchecked{System.err.println( badSqlEx.getMessage() ); System.err.println( "Bad SQL:" + badSqlEx.getSql() );}
DDL 語句與 Spring 和 JDBC
前面的代碼代碼清單演示了使用 Spring 框架來處理 DML 語句。Spring 框架提供了非常簡單的語義來支持 DDL 語句。代碼清單 10 和 11 演示了用來執行從 Oracle 數據庫中刪除和清除表的語句的標准 JDBC 代碼和 Spring 包裝的 JDBC 代碼。
代碼清單 10
Statement stmt = null; try{stmt = this.myConnection.createStatement(); stmt.executeUpdate("DROP TABLE salgrade PURGE"); }catch ( SQLException sqlEx ) {System.err.println("Message:" + sqlEx.getMessage()); System.err.println("Error Code:" + sqlEx.getErrorCode()); System.err.println("SQL State:" + sqlEx.getSQLState());} finally{try {if (stmt != null) {stmt.close(); } }catch (SQLException finallySqlEx) // checked exception {System.err.println(finallySqlEx.getMessage()); } }
代碼清單 11
try{JdbcTemplate jt = new JdbcTemplate(this.myDataSource); jt.execute("DROP TABLE salgrade PURGE"); } catch ( BadSqlGrammarException badSqlEx ) // unchecked{System.err.println( badSqlEx.getMessage() ); System.err.println( "BadSQL:" + badSqlEx.getSql() );}
這時應該很明顯,基於 Spring 的代碼比直接的 JDBC 代碼要更易於閱讀(以及編寫和維護)。實際上,這個代碼清單中只有兩行是絕對必需的,因為捕獲的異常是一個非強制異常。
利用 Spring 框架訪問存儲過程
代碼清單 13 和 14 分別演示了從直接的 JDBC 和 Spring 包裝的 JDBC 來訪問一個經過設計的存儲過程(如代碼清單 12 所示)。
代碼清單 12
CREATE OR REPLACE PROCEDURE salary_percentile (salary IN emp.sal%TYPE, low IN salgrade.losal%TYPE,high IN salgrade.hisal%TYPE,percentile OUT NUMBER ) ASBEGINIF salary < 0 THENpercentile := null; ELSEpercentile := (salary - low) / (high - low);END IF;END;
代碼清單 13
String escapeString = "{call salary_percentile (?,?,?,?)}" ;CallableStatement cStmt = null; double percentile = -1.0;final int PERCENTILE_INDEX = 4; try{cStmt = this.myConnection.prepareCall(escapeString); cStmt.setInt(1, aSalary); // aSalary passed into this methodcStmt.setInt(2, aLow); // aLow passed into this methodcStmt.setInt(3, aHigh); // aHigh passed into this methodcStmt.registerOutParameter (PERCENTILE_INDEX, Types.DOUBLE);cStmt.execute(); percentile = cStmt.getDouble(PERCENTILE_INDEX);} catch ( SQLException sqlEx ) {System.err.println("Message:" + sqlEx.getMessage()); System.err.println("Error Code:" + sqlEx.getErrorCode()); System.err.println("SQL State:" + sqlEx.getSQLState());} finally{if ( cStmt != null ) {try {cStmt.close(); }catch (SQLException finallySqlEx) {System.err.println(finallySqlEx.getMessage()); } }}return percentile;
代碼清單 14 中的使用基於 Spring 的代碼來訪問存儲過程的示例演示了 org.springframework.jdbc.object.StoredProcedure 類的用法。(包含這個 StoredProcedure 類的 Spring 程序包也包含了除存儲過程調用之外的其他類型的 SQL 語句的對象表示。關於其他 SQL 語句類型的對象程序包和對象表示的更多詳細信息,請參見 Spring 的文檔。)
代碼清單 14
private class SalaryCalculator extends StoredProcedure{ /** Name of procedure in database. */ public static final String PROC_NAME = "salary_percentile"; /*** Constructor for this StoredProcedure class.* @param ds Data Source. */public SalaryCalculator(DataSource ds) {setDataSource(ds); setSql(PROC_NAME); // Parameters should be declared in same order here that // they are declared in the stored procedure . declareParameter(new SqlParameter("salary", Types.DOUBLE)); declareParameter(new SqlParameter("low", Types.INTEGER)); declareParameter(new SqlParameter("high", Types.INTEGER)); declareParameter(new SqlOutParameter( "percentile",Types.DOUBLE ) ); compile(); } /*** Execute stored procedure.* @return Results of running stored procedure. */ public Map executeCalculation( double aSalary,int aLow,int aHigh ) {Map inParameters = new HashMap();inParameters.put( "salary", new Double(aSalary) );inParameters.put( "low", new Integer(aLow) ); inParameters.put( "high", new Integer(aHigh) ); Map out = execute( inParameters ); // Call on parent classreturn out; } } // . . . // Code below is all that is needed to call your Stored Procedure // created above. // . . .SalaryCalculator calcPercentile =new SalaryCalculator(this.myDataSource); Map calcResults =calcPercentile.executeCalculation(aSalary, aLow, aHigh); return ((Double)calcResults.get("percentile")).doubleValue(); // . . . // remainder of class// . . .
代碼清單 14 中的程序在單個類中表示代碼。該代碼清單中顯示的代碼的主體是一個擴展了 Spring 的 StoredProcedure 的內部類 (SalaryCalculator)。這個由開發人員創建的類包裝了代碼清單 12 中顯示的存儲過程。調用 SalaryCalculator 類只需要幾行代碼。因此,SalaryCalculator 類對調用存儲過程所涉及的大部分問題進行了抽象化。
雖然開發人員必須編寫擴展 StoredProcedure 的類,但這麼做(而不是直接編寫存儲過程訪問)將帶來相應的好處。一個好處是能夠使用 Spring 的特有的非強制 DAO 和 JDBC 異常而不是通用的強制 SQLException。此外,正如代碼代碼清單所示,處理關閉資源的煩瑣工作被抽象化了。
注意在將小於 0 的薪水值傳給存儲過程時代碼清單 13 和代碼清單 14 返回的結果的差異是很有趣的。在直接的 JDBC 的情況下(代碼清單 13),將返回值 0.0,而 wasNull() 是確定結果是否真正為空的唯一方式。在基於 Spring 的代碼中(代碼清單 14),這種情況下將返回一個 Java null,無需 wasNull() 調用。
利用 Spring 訪問 Oracle 對象
Spring Framework 的 JDBC 抽象可以與 Oracle 對象(例如在代碼清單 15 中創建的對象)結合使用。
代碼清單 15
CREATE OR REPLACE TYPE address_type AS OBJECT(STREET VARCHAR2(20),CITY VARCHAR2(15), STATE CHAR(2),ZIP CHAR(5)); /CREATE TABLE emp_address_table(EMP_NUM NUMBER(4),ADDRESS ADDRESS_TYPE);
利用 JDBC 訪問 Oracle 對象存在兩種常用的方法。一種方法是將標准的 JDBC 接口 java.sql.Struct 與其 Oracle 驅動程序特有的類實施 oracle.sql.STRUCT 結合使用。第二種方法是創建與 Oracle 對象類型映射的 Java 類。將 Oracle 對象與 Oracle JDeveloper 10g IDE 和 JPublisher 結合使用是件輕而易舉的事。
使用 java.sql.Struct 方法或 JPublisher 方法的一個有趣的“副作用”:如果您想在這些對象中訪問數據,那麼必須處理 SQLException。例如,使用 java.sql.Struct 方式,getAttributes() 方法將拋出 SQLException。同樣,JDeveloper/JPublisher 創建的 Java 類也將包含拋出 SQLException 的方法。訪問這些 Java 對象的開發人員將必須處理這些 SQLException,或者可以使用 Spring(如代碼清單 16 和 17 所示)。
代碼清單 16
String queryStr = "SELECT address FROM emp_address_table WHERE "+ "emp_num = " + aEmpNum; // aEmpNum passed infinal List addresses = new ArrayList(); JdbcTemplate jt = new JdbcTemplate(this.myDataSource); jt.query( queryStr,new RowCallbackHandler() {public void processRow(ResultSet rs)throws SQLException {// The Struct and ResultSet methods throw // SQLException, so throws above is necessaryjava.sql.Struct address =(Java.sql.Struct) rs.getObject(1); String street =address.getAttributes() [0].toString();String city =address.getAttributes() [1].toString();String state =address.getAttributes() [2].toString();String zipCode =address.getAttributes() [3].toString();String addressStr = street + ", " + city + ", "+ state + " " + zipCode;addresses.add( addressStr ); } } );
String updateStr = "UPDATE emp_address_table SET address = ? "+ "WHERE emp_num = ?"; JdbcTemplate jt = new JdbcTemplate( getDataSource() ); jt.update( updateStr,new PreparedStatementSetter() {public void setValues(PreparedStatement ps)throws SQLException {Address address = new Address();address.setStreet(aStreet); address.setCity(aCity); address.setState(aState);address.setZip(aZipCode); ps.setObject(1, address);ps.setInt(2, aEmpNum); } });
在利用 JDBC 訪問 Oracle 對象的兩種方式下(Struct 和 SQLData)都將使用拋出 SQLException 的方法來訪問返回的類。代碼清單 16 和 17 顯示了如何使用匿名內部回調類來將 SQLException 隱藏在特有的非強制 Spring 框架異常層次結構之後。這些重新拋出的異常利用了與本文中的其他示例相同的異常轉換。這些代碼清單不僅專門演示了如何處理 Oracle 對象的基於 Spring 的訪問,還演示了在其他的 JdbcTemplate 常規方法不適用時如何使用匿名內部回調類。
在代碼清單 16 中,您將在本文中第一次看到在基於 Spring 的代碼中出現 ResultSet 和 SQLException。不過,注意甚至在這些代碼中也沒有直接使用 SQLException。相反,Spring 框架通過其異常處理機制來處理任何拋出的 SQLException,您只需關心您希望捕獲和處理的 Spring 異常。
代碼清單 17 演示了本文中第一次在基於 Spring 的代碼中使用 PreparedStatement,並顯示了對 SQLException 的另一種引用。正如代碼清單 16 的情況一樣,SQLException 主要用於引用 Spring 框架的 JdbcTemplate 類,後者將處理它並將任何異常作為非強制 Spring 異常提供。
代碼清單 16 和 17 演示了 Spring 的 RowCallbackHandler 和 PreparedStatementSetter 回調接口的用法。在這些代碼清單中使用匿名內部類實現了這些接口。雖然與前面的代碼清單中顯示的 JdbcTemplate 的更簡單的用法相比,開發人員編寫的內部類必須知道關於 ResultSet 和 PreparedStatement 以及它們的各個 API 的更多信息,但您仍然無需關心 SQLException 的處理;JdbcTemplate 將執行異常處理。
前面的基於 Spring 的代碼清單(例如代碼清單 3 和 6 中使用的 JdbcTemplate)甚至沒有提到 ResultSet、Statement、PreparedStatement 或 SQLException。這些高度抽象的方法對於不想關心 JDBC 的具體用法的開發人員特別有用。不過,這些極其方便的方法沒有代碼清單 16 和 17 所演示的內部類方法靈活。代碼清單 16 和 17 中顯示的更靈活的方法可以在需要時使用(只需稍微了解基本的 JDBC API)。在所有情況下,異常處理都由 Spring 異常層次結構來一致地執行,您不需要關心 SQLException。
其他好處
Spring 框架為 JDBC 帶來的好處除上面稍微詳細介紹的之外,還存在其他幾個將 Spring 框架用於 JDBC 的好處(在此不再進一步討論)。這些好處包括:
Spring 的 JdbcTemplate 類支持其他幾個由 Spring 提供的與本文所討論的接口類似的接口。這些接口包括 ResultSetExtractor 和 PreparedStatementCreator,它們分別類似於 RowCallbackHandler 和 PreparedStatementSetter。JdbcTemplate 支持的許多接口中的另一個是非常有用的 BatchPreparedStatementSetter。
雖然不需要在 Spring 應用程序上下文中使用 Spring JDBC 抽象,但可以選擇將 JDBC 抽象用於該上下文,這提供了額外的好處。可以使用 Spring 框架通過配置文件來將數據訪問對象與業務對象相連(而不是在代碼中直接耦合)。
org.springframework.jdbc.object 程序包支持將關系數據庫操作(包括 DML 語句和存儲過程)作為可重用的對象。這些線程安全的對象為開發人員提供了更高級別的關系數據庫抽象(超過本文之前介紹和在所有代碼示例中使用的 Spring 的 JdbcTemplate)。我個人喜歡 JdbcTemplate 提供的抽象級別,我覺得它對例行問題(如異常、結果集和資源管理)的處理所提供的支持級別正是我想要的。不過,如果機構或其開發人員想享受更高級別的關系數據庫概念的抽象,而不必采用完整的對象-關系 (O/R) 映射技術(例如 TopLink 或 Hibernate),那麼 org.springframework.jdbc.object 程序包將值得考慮。代碼清單 12 中的存儲過程示例提供了對該程序包的簡單嘗試。
Spring 框架提供了一個可以在容器外部使用的簡單的 DataSource 實現,並提供了一個可以根據需要覆蓋的抽象數據源類(參見 Spring 參考文檔的第 10 章)。
除 JDBC 之外,Spring 框架還支持多種 O/R 映射技術(例如 Hibernate、iBatis 和 Java 數據對象 (JDO))。(參見 Spring 參考文檔的第 11 章。)本文底部的在線參考部分包含了一條到關於將 Spring 與 Oracle 的 TopLink 對象-關系映射產品結合使用的其他文章的鏈接。
Spring 框架簡化了共享相同的異常層次結構的技術不可知的 DAO 接口和實現的開發。(參見 Spring 參考文檔的第 9 章。)本文中的幾個地方簡單提到了這個主題。
Spring 框架提供了對在需要時將 SQLWarning 作為異常 (SQLWarningException) 來捕獲的支持。這對 Oracle 數據庫和 JDBC 驅動程序不是特別有用,因為它們傾向於對幾乎每一種情況拋出 SQLException。它對於需要提供警告並在使用一個更密集使用 SQLWarning 的數據庫的開發人員而言是一種更有用的特性。在這些情況下,直接使用 JDBC 的開發人員必須特別詢問是否出現了 SQLWarning。Spring 框架可以用來處理這些警告(和處理任何其他的 Spring 異常一樣),以實現更容易和更一致的處理。
Spring 框架提供了 org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor 接口和該接口的一些實現(例如 SimpleNativeJdbcExtractor)。當連接被另一個 DataSource 包裝或者要通過特定的連接池來獲取時,這些接口和實現對於通過 Oracle 連接或 ResultSet 來訪問 Oracle 特性非常有用。
Spring 提供了 org.springframework.jdbc.support.lob.OracleLobHandler 類來創建 oracle.sql.BLOB(二進制大對象)和 Oracle.sql.CLOB 的實例。
Spring 提供的 OracleSequenceMaxValueIncrementer 類提供了 Oracle 序列的下一個值。它可以有效地提供一些信息,這些信息與您直接使用以下命令時提供的信息相同:select someSequence.nextval from dual (其中 someSequence 是 Oracle 數據庫中的序列名稱)。該方法的一個好處是可以在 DAO 層次結構中使用 DataFIEldMaxValueIncrementer 接口,而無需緊密地耦合 Oracle 特有的實現。
Spring 框架的其他用途
本文專注於使用 Spring 來編寫更易於維護和更不易出錯的 JDBC 代碼。Spring 框架還可以用來做比這更多得多的事情。除了能夠為 JDBC 做和許多對象-關系映射技術所能做的同樣的事情之外,Spring 還擁有支持可以在企業應用程序中找到的所有其他層的特性。這些特性包括輕量級的 IoC(反轉控制)容器支持、一個 Web 框架和其他的 J2EE 應用程序支持。
Spring 框架被設計為非侵入性或者最小限度的侵入性。您可以使用 Spring 框架來做許多事情,但並不強迫應用程序全依賴於 Spring 框架。因為 Spring 是開放源代碼的,是一個很有用的工具,可作為優秀應用程序設計准則的具體示例。即使目前您不能直接在應用程序中使用 Spring 框架,學習使用 Spring 框架作為優秀設計和代碼准則的示例也是很有收獲的。Spring 提供的 JDBC 支持不需要使用 Spring 的任何其他特性,是極佳的 Spring 框架使用入門途徑,它不用與 Spring 的其他部分緊密結合。