初入學習 JDBC 運算元據庫,想必大家都寫過下面的程式碼:
資料庫為:H2
如果需要處理特定 SQL 異常,比如 SQL 語句錯誤,這個時候我們應該怎麼辦?
檢視 SQLException 原始碼,我們可以發現兩個重要的方法。
SQLException.getErrorCode:返回資料庫特定的錯誤碼,由資料庫廠商制定,不同廠商錯誤碼不同。如重複主鍵錯誤碼在 MySQL 中是 1062,而在 Oracle 中卻是 1。
SQLException.getSQLState:返回 XOPEN 或 SQL:2003 制定的錯誤碼規範。資料庫廠商會將不同錯誤訊息對映成同一個錯誤碼
所以我們可以根據 SQLException.getErrorCode 處理相應的資料庫異常。
由於資料庫廠商錯誤碼不相同,這就導致如果我們更換資料庫,上面判斷邏輯就必須重寫。
下面我們使用 Spring 運算元據庫。
Spring 運算元據庫
使用 Spring 之後,我們不再需要強制捕獲異常。如果 SQL 語句執行存在異常,Spring 會丟擲其內建特定的異常。如上面 SQL 語句異常將會丟擲 BadSqlGrammarException。除了這個異常之外,Spring 還定義很多資料庫異常。
每個 Spring 資料庫異常的基類都是 DataAccessException。由於 DataAccessException 繼承自 RuntimeException,所以在這類異常無需強制捕獲。
在 Spring 中使用 SQLExceptionTranslator 進行異常轉換,預設的轉換規則會根據 SQLException.getErrorCode 返回的錯誤碼進行相應的轉換。
下面我們從原始碼分析轉換過程。
實現細節
除錯 JdbcTemplate 的原始碼。
可以看到這裡捕獲了 SQLException,轉換之後再將其丟擲。
整個轉換過程,最後交給 SQLExceptionTranslator 進行轉換。
首先我們檢視 SQLExceptionTranslator 類圖。
可以看到其實現了一個抽象類以及三個子類。
抽象類中會首先會使用子類轉換,若未能轉換成功,將會啟動 fallback機制,再次轉換,作為兜底。
接著我們先看下三個子類的區別。
SQLErrorCodeSQLExceptionTranslator:
- 預設轉換類
- 主要根據 SQLException.getErrorCode 進行轉換。
- 預設使用 SQLExceptionSubclassTranslator 作為 fallback 物件。
SQLExceptionSubclassTranslator:
- 基於 JDBC 的 SQLException 標準子類判斷,如 java.sql.SQLTransientException。
- 使用 SQLStateSQLExceptionTranslator 作為 fallback 物件。
SQLStateSQLExceptionTranslator:
- 基於 SQLException.getSQLState 規則判斷。
下面分析 SQLErrorCodeSQLExceptionTranslator ,其他兩個比較類似,同學們可以自己看原始碼分析。
SQLErrorCodeSQLExceptionTranslator 轉換器主要根據 SQLException.getErrorCode 進行判斷。Spring 預設在 org/springframework/jdbc/support/sql-error-codes.xml 歸納不同資料庫廠商相關錯誤碼。該配置檔案會在第一次發生 SQL 異常時由 SQLErrorCodesFactory 進行載入,最後生成 SQLErrorCodes。
另外在 SQLErrorCodes 提供擴充套件方法,可以根據錯誤碼轉換成自定義的異常。
最後檢視 SQLErrorCodeSQLExceptionTranslator 裡的轉換方法。
前三個方法是 Spring 留下擴充套件方法,可以根據自己需求分別擴充套件。若都沒有實現,將會根據錯誤碼判斷轉換成具體的異常。
自定義異常轉換
上面說到 Spring 總共給我們留下三處擴充套件點。
- 繼承 SQLErrorCodeSQLExceptionTranslator,重寫 customTranslate。
- 繼承 SQLExceptionTranslator,重寫 translate,然後在 sql-error-codes.xml注入。
- 使用 SQLErrorCodes#customTranslations ,然後在 sql-error-codes.xml 配置相關錯誤碼轉換的規則。
第三種方式改動最小,比較簡單。首先在 classpath 下生成 sql-error-codes.xml,複製原有配置,最後配置 customTranslations 。
這裡需要注意的是,需要轉化的異常型別必須為 DataAccessException 子類。下面面我們自定義一個異常。
總結
Spirng 異常處理將 SQL 異常轉化成內建異常,遮蔽不同資料庫返回碼不一致的帶來的問題。
最後總結本文的知識點,希望幫助到大家。