Spring 提供了一套和實(shí)現(xiàn)技術(shù)無關(guān)的 、 面向 DAO 層語義級(jí)別的異常體系柒凉,內(nèi)部通過轉(zhuǎn)換器將不同持久化技術(shù)的異常轉(zhuǎn)換成 Spring 的異常钳宪,實(shí)現(xiàn)統(tǒng)一管理。
1 異常體系
很多正統(tǒng)的 AP中寨辩,使用了過多的檢查型異常,以致于在使用 API 時(shí)歼冰,代碼中充斥了大量 try/catch 樣板式的代碼 靡狞。 大多數(shù)情況下,這些 catch 代碼段除了記錄日志外停巷,并沒有做多少其它有益的工作耍攘。
比如 JDK 中的 JDBC API榕栏,大家都說不好用,因?yàn)闄z查型異常泛濫蕾各,許多異常處理代碼喧賓奪主地侵入到業(yè)務(wù)代碼中扒磁,從而破壞了整體代碼的整潔與優(yōu)雅 。
Spring 在 org.springframework.dao
中提供了一套優(yōu)雅的 DAO 異常體系式曲, 這些異常都繼承自 DataAccessException
妨托, DataAccessException
繼承自NestedRuntimeException
, NestedRuntimeException
異常以嵌套的方式封裝了源異常 。 因此吝羞,雖然不同的持久化技術(shù)的特定異常被轉(zhuǎn)換到 Spring 的 DAO 異常體系中兰伤,但我們可以通過 getCause()
方法獲取原始異常信息 。
這套異常體系從 DAO 的抽象層次上定義了異常目錄樹钧排,它使得開發(fā)者可以很容易地關(guān)注某個(gè)特定的語義異常敦腔。而 JDBC 的 SQLException 過于底層,而且與具體數(shù)據(jù)庫強(qiáng)相關(guān)(比如 getErrorCode()
)恨溜,不僅不好編碼符衔,而且很難移植。
Spring 建立了異常分類目錄糟袁,以適當(dāng)?shù)念w粒度劃分了異常類型判族。這樣做的好處是:
- 開發(fā)者可以從底層繁瑣復(fù)雜的技術(shù)細(xì)節(jié)中解脫出來。
- 開發(fā)者可以選擇自己感興趣的異常進(jìn)行處理 项戴。
DataAccessException 下有這些異常子類:
異常子類 | 說明 |
---|---|
CleanupFailureDataAccessException | 執(zhí)行 DAO 操作成功形帮,但在釋放數(shù)據(jù)資源時(shí)發(fā)生異常,如關(guān)閉 Connection 時(shí)發(fā)生異常周叮。 |
ConcurrencyFailureException | 并發(fā)地操作數(shù)據(jù)時(shí)發(fā)生異常辩撑,如無法獲取樂觀鎖或悲觀鎖時(shí)、死鎖引發(fā)的失敗等場景则吟。 |
DataAccessResourceFailureException | 訪問數(shù)據(jù)資源失敗槐臀,如無法獲取數(shù)據(jù)連接锄蹂,無法獲取 Hibernate 的會(huì)話等場景氓仲。 |
DataRetrievalFailureException | 獲取數(shù)據(jù)失敗,如找不到對(duì)應(yīng)主鍵的數(shù)據(jù)或使用了錯(cuò)誤的列索引等場景得糜。 |
DataSourceLookupFailureException | 無法從 JNDI 中查找到數(shù)據(jù)源敬扛。 |
DataIntegrityViolationException | 數(shù)據(jù)操作違反了數(shù)據(jù)一致性限制時(shí)拋出,如插入重復(fù)的主鍵或引用不存在的外鍵場景朝抖。 |
InvalidDataAccessApiUsageException | 不正確地調(diào)用某一種持久化技術(shù)時(shí)拋出啥箭,如在 Spring JDBC 中查詢對(duì)象在調(diào)用前沒有事先進(jìn)行編譯操作,就會(huì)拋出該異常治宣。這種異常主要是因?yàn)椴徽_地使用持久化技術(shù)而產(chǎn)生的急侥。 |
InvalidDataAccessResourceUsageException | 在訪問數(shù)據(jù)源時(shí)使用了不正確的方法時(shí)拋出砌滞,如寫錯(cuò) SQL 語句。 |
PermissionDeniedDataAccessException | 數(shù)據(jù)訪問權(quán)限不足時(shí)拋出坏怪。如僅擁有只讀權(quán)限卻試圖更改數(shù)據(jù)贝润。 |
UncategorizedDataAccessException | 其它未被分類的異常。 |
Spring 為了進(jìn)一步細(xì)化錯(cuò)誤問題域铝宵, 它對(duì)上述的這些一級(jí)異常類又進(jìn)行了細(xì)分打掘。
這套異常體系具有高度的可擴(kuò)展性,當(dāng) Spring 需要對(duì)一個(gè)新的持久化技術(shù)提供支持時(shí)鹏秋,只要為其定義一個(gè)對(duì)應(yīng)的子異常即可尊蚁,這種方式實(shí)現(xiàn)了設(shè)計(jì)模式中的“開閉原則” 。
開閉原則( OCP )是面向?qū)ο笤O(shè)計(jì)中 “ 可復(fù)用設(shè)計(jì) ” 的基石侣夷,是面向?qū)ο笤O(shè)計(jì)中最重要的原則之一横朋,其它很多的設(shè)計(jì)原則都是實(shí)現(xiàn)開閉原則的一種手段 。 對(duì)于擴(kuò)展是開放的百拓,對(duì)于修改是關(guān)閉的叶撒,這意味著模塊的行為是可以擴(kuò)展的 。 當(dāng)應(yīng)用的需求改變時(shí)耐版,我們可以對(duì)模塊進(jìn)行擴(kuò)展祠够,使其具有滿足那些改變的新行為 。
2 異常轉(zhuǎn)換器
2.1 JDBC
一般情況下粪牲,JDBC API 在執(zhí)行數(shù)據(jù)操作出現(xiàn)異常時(shí)古瓤,大都會(huì)拋出 SQLException ,SQLException 把異常的細(xì)節(jié)封裝在異常屬性中腺阳,所以如果希望了解異常的具體原因落君,我們必須對(duì)異常屬性進(jìn)行分析。
SQLException 擁有兩個(gè)代表異常具體原因的屬性:
屬性 | 類型 | 說明 |
---|---|---|
錯(cuò)誤碼 | int | 與具體數(shù)據(jù)庫相關(guān)亭引,調(diào)用 getErrorCode() 返回绎速。 |
SQL 狀態(tài)碼 | String | 標(biāo)準(zhǔn)錯(cuò)誤代碼,由 5 個(gè)字符組成,調(diào)用 getSQLState() 返回焙蚓。 |
Spring 會(huì)根據(jù)錯(cuò)誤碼和 SQL 狀態(tài)碼將 SQLExeption 轉(zhuǎn)換為對(duì)應(yīng)的 Spring DAO 異常 纹冤。 在 org.springframework.jdbc.support
包中定義了 SQLExceptionTranslator
接口,該接口的兩個(gè)實(shí)現(xiàn)類 SQLErrorCodeSQLExceptionTranslator
和 `SQLStateSQLExceptionTranslator
分別負(fù)責(zé)處理 SQLException 中錯(cuò)誤代碼和 SQL 狀態(tài)碼的轉(zhuǎn)換工作 购公。
2.2 其它 ORM 持久化技術(shù)
其它 ORM 持久化技術(shù)都擁有一個(gè)語義明確的異常體系萌京,所以轉(zhuǎn)換相對(duì)簡單。
注意:Spring4 只支持 Hibernate3.6+宏浩。
Spring 在 org.springframe.orm
中為所支持的 ORM 技術(shù)定義了相應(yīng)的子包知残。對(duì)應(yīng)的異常轉(zhuǎn)換器也定義在這些子包中:
ORM 持久化技術(shù) | 異常轉(zhuǎn)換器 |
---|---|
HibernateX,X 可為 3比庄、4 或 5 | org.springframework.orm.hibernateX.SessionFactoryUtils |
JPA | org.springframework.orm.jpa.EntityManagerFactoryUtils |
JDO | org.springframework.orm.jdo.PersistenceManagerFactoryUtils |
這些工具類除了具有異常轉(zhuǎn)換的功能外求妹,在進(jìn)行事務(wù)管理時(shí)乏盐,還提供了從事務(wù)上下文環(huán)境中返回相同會(huì)話的功能 。
Spring 也支持 myBatis 持久化技術(shù)制恍,因?yàn)?myBatis 拋出的異常與 JDBC 相同丑勤, 都是 SQLException 異常,所以采用了和 JDBC 相同的異常轉(zhuǎn)換器 吧趣。