概述
【IT168 專稿】
近幾年持久化技術領域異常喧
囂,各種框架雨後春筍般地冒出,Sun也連接不斷地頒佈幾個持久化規範。Spring對多個持久化技術提供了集成的支持,包括Hibernate、
iBatis、JDO、JPA、TopLink,此外,還通過Spring JDBC框架對JDBC
API進行簡化。Spring面向DAO制定了一個通用的異常體系,屏蔽具體持久化技術的異常,使業務層和具體的持久化技術達到解耦。此外,Spring
提供了模板類簡化各種持久化技術的使用。通用的異常體系及模板類是Spring整合各種五花八門持久化技術的不二法門,Spring不但藉此實現了對多種
持久化技術的整合,還可以不費吹灰之力整合潛在的各種持久化框架,體現了“開-閉原則”的經典應用。
Spring的DAO理念
DAO(Data Access
Object)是用於訪問數據的對象,雖然我們在大多數情況下,將數據保存在數據庫中,但這並不是唯一的選擇,你也可以將數據存儲到文件中或LDAP中。
DAO不但屏蔽了數據存儲的最終介質的不同,也屏蔽了具體的實現技術的不同。
早期,JDBC是訪問數據庫的主流選擇,近幾年,數據持久技術獲得
了長足的發展,Hibernate、iBatis、JPA、JDO成爲持久層中爭放異彩的實現技術。只要爲數據訪問定義好DAO接口,並使用具體的技術實
現DAO接口的功能,你就可以在不同的實現技術間平滑的切換。
圖 1 業務層通過DAO接口訪問數據
圖 1是一個典型的DAO應用實例,在UserDao中定義訪問User數據對象的接口方法,業務層通過UserDao操作數據,並使用具體持久技術實現UserDao接口方法,這樣業務層和具體持久化技術就實現瞭解耦。
提供DAO層的抽象可以帶來一些好處,首先,我們可以很容易地構造模擬對象,方便單元測試的開展,其次在使用切面時,我們有更多的選擇:既可以使用JDK動態代理也可以使用CGLib動態代理。
Spring本質上希望以統一的方式整合底層的持久化技術:以統一的方式進行調用及事務管理,避免讓具體的實現侵入到業務層的代碼中。由於每個持久化實現
技術都有各自的異常體系,所以Spring提供了統一的異常體系,使不同異常體系的阻抗得以彌消,方便定義出和具體實現技術無關的DAO接口,以及整合到
相同的事務管理體系中。
統一的異常體系
統一的異常體系是整合不同的持久化實現技術的關鍵,Spring提供了一套和實現技術無關的、面向於DAO層次語義的異常體系,並通過轉換器將不同的持久化技術異常轉換成Spring的異常。
Spring的DAO異常體系
在很多正統API或框架中,檢查型異常被過多的使用,以至在使用API時,代碼裏充斥着大量的try/catch樣板式的代碼。在很多情況下,除了在
try/catch中記錄異常信息以外,我們並沒有做多少實質性的工作。引發異常的問題往往是不可恢復的,如數據連接失敗,SQL語句存在語法錯誤,強制
捕捉的檢查型異常除了限制開發人員的自由度以外,並沒有提供什麼有意義的作用。因此,Spring的異常體系都是建立在運行期異常的基礎上,開發者可以根
據需要捕捉感興趣的異常。
JDK很多API之所以難用,一個很大的原因就是檢查型異常的泛濫,如JavaMail、EJB以及JDBC等等,使用這些API,一堆堆異常處理的代碼喧賓奪主式地侵入業務代碼中,破壞了代碼的整潔和優雅。
Spring在org.springframework.dao包中提供了一套完備優雅的DAO異常體系,這些異常都繼承於
DataAccessException,而DataAccessException本身又繼承於
NestedRuntimeException,NestedRuntimeException異常以嵌套的方式封裝了源異常。因爲雖然不同持久化技術的
特定異常被轉換到Spring的DAO異常體系中,原始的異常信息並不會丟失,只要你願意,就可以方便地通過getCause()方法獲取原始的異常信
息。
Spring的DAO異常體系並不和具體的實現技術相關,它從DAO概念的抽象層面定義了異常的目錄樹。在所有的持久化框架中,我們並沒有發現擁有如此豐
富語義異常體系的框架,Spring這種設計無疑是獨具匠心的,它使得開發人員關注某一特定語義的異常變得容易。在JDBC中的SQLException
中,你必須通過異常的getErrorCode()或getSQLState()獲取錯誤代碼,直接根據這些代碼判斷是錯誤的類型,這種過於底層的API
不但帶來了代碼編寫上的難度,而且也使代碼的移植變得困難,因爲getErrorCode()是數據庫相關的。
Spring以分類手法建立了異常分類目錄,對於大部分應用來說,這個異常分類目錄對異常類型的劃分具有適當的顆粒度。一方面,使開發者從底層細如針麻的
技術細節中脫身出來,另一方面,可以從這個語義豐富的異常體系中選擇感興趣的異常加以處理。圖 2列出了那些位於Spring
DAO異常體系第一層次的異常類,每個異常類下還可能擁有衆多的子異常類:
圖 2 Spring DAO異常體系
Spring DAO異常體系類非常豐富,這裏,我們僅列出DataAccessException異常類下的子類。我們可以很容易地通過異常類的名字瞭解到異常所代表的語義。我們通過下表對這些異常進行簡單的描述:
表 1 Spring DAO異常體系類
異常
|
說明
|
CleanupFailureDataAccessException
|
DAO 操作成功執行,但在釋放數據資源時發生異常,如關閉 Connection 時發生異常等。
|
ConcurrencyFailureException
|
表示在進行併發數據操作時發生異常,如樂觀鎖無法獲取、悲觀鎖無法獲取、死鎖引發的失敗等待異常。
|
DataAccessResourceFailureException
|
訪問數據資源時失敗,如無法獲取數據連接,無法獲取 Hibernate 的會話等。
|
DataRetrievalFailureException
|
獲取數據失敗,如找不到對應主鍵的數據,使用了錯誤的列索引等。
|
DataSourceLookupFailureException
|
無法從 JNDI 中查找到數據源。這個異常是 Spring 2.0 新增的。
|
DataIntegrityViolationException
|
當數據操作違反了數據一致性限制時拋出的異常,如插入重複的主鍵,引用不存在的外鍵等。
|
InvalidDataAccessApiUsageException
|
不正確地調用某一持久化技術時拋出的異常,如在 Spring JDBC 中查詢對象在調用前必須進行編譯操作,如果忘記這項操作將會產生該異常。這種異常不是由底層數據資源產生,而是由不正確地使用持久化技術產生的。
|
InvalidDataAccessResourceUsage
|
在訪問數據源時使用了不正確的方法所拋出的異常,如 SQL 語句錯誤將拋出該異常。 |
PermissionDeniedDataAccessException
|
數據訪問時由於權限不足引發的異常。如用僅擁有隻讀權限用戶試圖進行數據更改操作將拋出該異常。該異常是 Spring 2.0 新增的。
|
UncategorizedDataAccessException
|
其它未分類的異常都歸到該異常中。 |
爲了進一步細化錯誤的問題域,Spring對一級異常類進行子類的細分,如InvalidDataAccessResourceUsageException就擁有10多個子類,下面是其中3個子類:
圖 3 一級異常類的細化
對於InvalidDataAccessResourceUsageException異常,不同的持久化實現技術均有對應的子異常類。如
BadSqlGrammarException對應JDBC實現技術SQL語句語法錯誤的異常,而HibernateQueryExcpetion和
TopLinkQueryException分別對應Hibernate和TopLink實現技術的查詢語法異常。
Spring的這個異常體系具有高度的可擴展性,當Spring需要對一個新的持久化技術提供支持時,只要定義爲其定義一個對應的子異常就可以了,這種更改完全滿足設計模式中的開-閉原則。
雖然Spring定義瞭如此豐富的異常類,作爲開發人員,我們僅需要對感興趣的異常進行處理就可以了。假設某一個項目要求在發生樂觀鎖異常時,嘗試再次獲
取樂觀鎖非不是直接返回錯誤。那麼,我們只需要在代碼中顯式捕捉ConcurrencyFailureException異常,然後在catch代碼塊中
編寫滿足需求的邏輯即可。其它衆多的異常則可以簡單地交由框架自動處理(如發生運行期異常時自動回滾事務)。
JDBC的異常轉換器
傳統的JDBC API在發生幾乎所有的數據操作問題都拋出相同的SQLException,它將異常的細節性信息封裝在異常屬性中,所以如果希望瞭解異常的具體原因,你必須分析異常對象的信息。
SQLException擁有兩個代表異常具體原因的屬性:錯誤碼和SQL狀態碼,前者是數據庫相關的,可通過getErrorCode()返回,其值的
類型是int;而後者是一個標準的錯誤代碼,可通過getSQLState()返回,是一個String類型的值,由5字符組成。
Spring根據錯誤碼和SQL狀態碼信息將SQLExeption翻譯成Spring
DAO的異常體系。在org.springframework.jdbc.support包中定義了SQLExceptionTranslator接口,
該接口的兩個實現類SQLErrorCodeSQLExceptionTranslator和
SQLStateSQLExceptionTranslator分別負責處理SQLException中錯誤代碼和SQL狀態碼的翻譯工作。將
SQLException翻譯成Spring
DAO異常體系的工作是比較艱辛的,但Spring框架替我們完成這項艱鉅的工作並保證轉換的正確性,我們有充分的理由依賴這個轉換的正確性。
其它持久技術的異常轉換器
由於各種框架級的持久化技術都擁有一個語義明確的異常體系,所以將這些異常轉換爲Spring DAO的體系相對輕鬆一些。下面,我們將對不同持久化技術的異常轉換器進行學習。
由於Hibernate
3.0版本和低版本不兼容,Spring分別爲這兩個版本分別提供了支持(這也可以看出Hibernate在Spring中所享受的特殊禮遇)。在
org.springframework.orm包中,分別爲Spring所支持的ORM持久化技術定義了一個子包,在這些子包中提供相應ORM技術的整
合類。Spring爲各個ORM持久化技術所提供的異常轉換器在表 2中說明:
表 2 各ORM持久化技術異常轉換器
ORM 持久化技術
|
異常轉換器
|
Hibernate
|
org.springframework.orm.hibernate.SessionFactoryUtils
|
Hibernate 3.0
|
org.springframework.orm.hibernate3.SessionFactoryUtils
|
JPA
|
org.springframework.orm.jpa.EntityManagerFactoryUtils
|
JDO
|
org.springframework.orm.jdo.PersistenceManagerFactoryUtils
|
TopLink
|
org.springframework.orm.toplink.SessionFactoryUtils
|
ORM持久化技術 異常轉換器
Hibernate org.springframework.orm.hibernate.SessionFactoryUtils
Hibernate 3.0 org.springframework.orm.hibernate3.SessionFactoryUtils
JPA org.springframework.orm.jpa.EntityManagerFactoryUtils
JDO org.springframework.orm.jdo.PersistenceManagerFactoryUtils
TopLink org.springframework.orm.toplink.SessionFactoryUtils
這些工具類除了具有異常轉換的功能外,在進行事務管理時,還提供了從事務上下文中返回相同會話的功能。
Spring也支持iBatis ORM持久化技術,由於iBatis拋出的異常是和JDBC相同的SQLException異常,所以直接採用和JDBC相同的異常轉換器。
統一數據訪問模板
到一個餐館用餐,大抵都會經歷這個的一個流程,進入餐館->迎賓小姐問候並引到適合的位置->抄起菜單點菜>用餐->買單
->離開餐館。之所以我們喜歡時不時下下館子,就是因爲我們只要點菜->用餐->買單就可以了,幕後的烹飪製作、刷鍋洗盤等工作我們完
全不用關心,一切已經由餐館服務人員按照服務流程按部就班,有條不紊地執行了。衡量一個餐館服務質量好壞的一個重要標準是我們無須關心他們所負責流程:不
用催問菜爲什麼還沒有上好(不但快而且服務態度佳),不用關心盤子爲什麼不乾淨(不但乾淨而且已經進行了消毒)。
從某種角度看,與其說餐館爲我們提供了服務,還不如說我們參與到餐館的流程中:不管什麼顧客點的菜都由相同的櫥師烹製,不管什麼顧客都按單付錢。在幕後,
餐館擁有一個服務的模板,模板中定義的流程可以用於應付所有的顧客,只要爲顧客提供幾個專有需求(點的菜也可不一樣,座位可以自由選擇),其它一切都按模
板化的方式處理。
在直接使用具體的持久化技術時,我們大多需要處理整個流程,並沒有享受餐館用餐式的便捷。Spring爲支持的持久化技術分別提供了模板訪問的方式,降低了使用各種持久化技術的難度,可以大幅提高開發效率。
使用模板和回調機制
下面是一段使用JDBC進行數據訪問操作的簡單代碼,我們已經儘可能簡化整個過程的處理了,但以下的步驟幾乎都是不可或缺的。
代碼清單 1 JDBC數據訪問
如以上數據訪問代碼所示,JDBC數據訪問操作按以下的流程進行:
1. 準備資源;
2. 啓動事務;
3. 在事務中執行具體數據訪問操作;
4. 返回數據;
5. 提交/回滾事務;
6. 關閉資源,處理異常。
按照傳統的方式,編寫任何帶事務的數據訪問的程序時,你都需要重複編寫上面的代碼,而其中只有粗體部分所示的代碼是業務相關的,而其它的代碼都是在做一些例行公事,因而導致了大量八股文式的代碼充斥着整個程序。
Spring
將這個相同的數據訪問流程固化到模板類中,並將數據訪問中固定和變化的部分分開,同時保證模板類是線程安全,以便多個數據訪問線程共享同一模板實例。固定
的部分在模板類中已經準備好,而變化的部分通過回調接口開放出來,用於定義具 體數據訪問和結果返回的操作。圖
4描述了模板類是如何拆分固定和變化部分的邏輯:
圖 4 Spring DAO模板和回調
這樣,我們只要編寫好回調接口,並調用模板類進行數據訪問,就可以得到預想的結果:數據訪問成功執行,前置和後置的樣板化工作也得到順序的執行,在提高開發效率的同時保證了資源使用的正確性,徹底消除因忽視資源釋放而引起的資源泄漏的問題。
Spring爲不同持久化技術所提供的模板類
Spring爲各種支持的持久化技術都提供了簡化操作的模板和回調,在回調中編寫具體的數據操作邏輯,使用模板執行數據操 作,在Spring中,這是典型的數據操作模式。下面,我們來了解一下Spring爲不同的持久化技術所提供的模板類。
表 3 不同持久化技術對應的模板類
ORM持久化技術 模板類
JDBC org.springframework.jdbc.core. JdbcTemplate
Hibernate org.springframework.orm.hibernate.HibernateTemplate
Hibernate 3.0 org.springframework.orm.hibernate3.HibernateTemplate
iBatis org.springframework.orm.ibatis.SqlMapClientTemplate
JPA org.springframework.orm.jpa.JpaTemplate
JDO org.springframework.orm.jdo.JdoTemplate
TopLink org.springframework.orm.jpa. JpaTemplate
如果你使用JDK 5.0或以上版本,則可以使用jdbc.core.simple.SimpleJdbcTemplate的模板類,該模板類使用了泛型和不定入數的技術,使模板的功能更加強大,更易於使用。
如果,我們直接使用模板類,一般都需要在DAO中定義一個模板對象並提供數據資源,Spring爲每一個持久化技術都提供了支持類,支持類中已經爲我們完成這樣的功能。這樣,我們只需要擴展這些支持類就可以直接編寫實際的數據訪問邏輯,沒有須臾的阻隔。
不同持久化技術的支持類如表 4所示:
ORM 持久化技術
|
支持類
|
JDBC
|
org.springframework.jdbc.core. JdbcDaoSupport
|
Hibernate
|
org.springframework.orm.hibernate.HibernateDaoSupport
|
Hibernate 3.0
|
org.springframework.orm.hibernate3.HibernateDaoSupport
|
iBatis
|
org.springframework.orm.ibatis.SqlMapClientDaoSupport
|
JPA
|
org.springframework.orm.jpa.JpaDaoSupport
|
JDO
|
org.springframework.orm.jdo.JdoDaoSupport
|
TopLink
|
org.springframework.orm.jpa. JpaDaoSupport
|
表 4 持久化技術的支持類
ORM持久化技術 支持類
JDBC org.springframework.jdbc.core. JdbcDaoSupport
Hibernate org.springframework.orm.hibernate.HibernateDaoSupport
Hibernate 3.0 org.springframework.orm.hibernate3.HibernateDaoSupport
iBatis org.springframework.orm.ibatis.SqlMapClientDaoSupport
JPA org.springframework.orm.jpa.JpaDaoSupport
JDO org.springframework.orm.jdo.JdoDaoSupport
TopLink org.springframework.orm.jpa. JpaDaoSupport
這些支持類都繼承於dao.support.DaoSupport類,DaoSupport實現了InitializingBean接口,在afterPropertiesSet()接口方法中檢查模板對象和數據源是否被正確設置,否則將拋出異常。
所有的支持類都是abstract的,其目的是希望被繼承使用,而非直接使用。
小結
Spring 支持目前大多數常用的數據持久化技術,Spring定義了一套面向DAO層的異常體系,併爲各種支持的持久化技術提供了異常轉換器。這樣,我們在設計DAO接口時,就可以拋開具體的實現技術,定義統一的接口。
不
管採用何種持久化技術,訪問數據的流程是相對固定的。Spring將數據訪問流程劃分爲固定和變化兩部分,並以模板的方式定義好流程,用回調接口將變化的
部分開放出來,留給開發者自行定義。這樣,我們僅需要提供業務相關的邏輯就可以完成整體的數據訪問了。Spring爲了進一步簡化持久化模板類的調整,爲
各種持久化技術提供了使用模板的支持類,支持類不但包含數據訪問模板,還包含數據源或會話。通過擴展支持類定義自己的數據訪問類是最簡單的數據訪問方式。