從原始碼解析 Spring 資料庫異常抽理

good_andyxu發表於2019-05-25

初入學習 JDBC 運算元據庫,想必大家都寫過下面的程式碼:

資料庫為:H2

JDBC 運算元據庫

如果需要處理特定 SQL 異常,比如 SQL 語句錯誤,這個時候我們應該怎麼辦?

檢視 SQLException 原始碼,我們可以發現兩個重要的方法。

SQLException.getErrorCode:返回資料庫特定的錯誤碼,由資料庫廠商制定,不同廠商錯誤碼不同。如重複主鍵錯誤碼在 MySQL 中是 1062,而在 Oracle 中卻是 1。

SQLException.getSQLState:返回 XOPENSQL:2003 制定的錯誤碼規範。資料庫廠商會將不同錯誤訊息對映成同一個錯誤碼

所以我們可以根據 SQLException.getErrorCode 處理相應的資料庫異常。

JDBC 異常處理

由於資料庫廠商錯誤碼不相同,這就導致如果我們更換資料庫,上面判斷邏輯就必須重寫。

下面我們使用 Spring 運算元據庫。

Spring 運算元據庫

Spring 資料庫處理

使用 Spring 之後,我們不再需要強制捕獲異常。如果 SQL 語句執行存在異常,Spring 會丟擲其內建特定的異常。如上面 SQL 語句異常將會丟擲 BadSqlGrammarException。除了這個異常之外,Spring 還定義很多資料庫異常。

Spring 資料庫異常

每個 Spring 資料庫異常的基類都是 DataAccessException。由於 DataAccessException 繼承自 RuntimeException,所以在這類異常無需強制捕獲。

在 Spring 中使用 SQLExceptionTranslator 進行異常轉換,預設的轉換規則會根據 SQLException.getErrorCode 返回的錯誤碼進行相應的轉換。

下面我們從原始碼分析轉換過程。

實現細節

除錯 JdbcTemplate 的原始碼。

JdbcTemplate

可以看到這裡捕獲了 SQLException,轉換之後再將其丟擲。

整個轉換過程,最後交給 SQLExceptionTranslator 進行轉換。

首先我們檢視 SQLExceptionTranslator 類圖。

類圖

可以看到其實現了一個抽象類以及三個子類。

抽象方法

抽象類中會首先會使用子類轉換,若未能轉換成功,將會啟動 fallback機制,再次轉換,作為兜底。

接著我們先看下三個子類的區別。

SQLErrorCodeSQLExceptionTranslator:

  1. 預設轉換類
  2. 主要根據 SQLException.getErrorCode 進行轉換。
  3. 預設使用 SQLExceptionSubclassTranslator 作為 fallback 物件。

SQLExceptionSubclassTranslator

  1. 基於 JDBC 的 SQLException 標準子類判斷,如 java.sql.SQLTransientException
  2. 使用 SQLStateSQLExceptionTranslator 作為 fallback 物件。

SQLStateSQLExceptionTranslator

  1. 基於 SQLException.getSQLState 規則判斷。

下面分析 SQLErrorCodeSQLExceptionTranslator ,其他兩個比較類似,同學們可以自己看原始碼分析。

SQLErrorCodeSQLExceptionTranslator 轉換器主要根據 SQLException.getErrorCode 進行判斷。Spring 預設在 org/springframework/jdbc/support/sql-error-codes.xml 歸納不同資料庫廠商相關錯誤碼。該配置檔案會在第一次發生 SQL 異常時由 SQLErrorCodesFactory 進行載入,最後生成 SQLErrorCodes

SQLErrorCodes

另外在 SQLErrorCodes 提供擴充套件方法,可以根據錯誤碼轉換成自定義的異常。

最後檢視 SQLErrorCodeSQLExceptionTranslator 裡的轉換方法。

SQLErrorCodeSQLExceptionTranslator

前三個方法是 Spring 留下擴充套件方法,可以根據自己需求分別擴充套件。若都沒有實現,將會根據錯誤碼判斷轉換成具體的異常。

預設轉換規則

自定義異常轉換

上面說到 Spring 總共給我們留下三處擴充套件點。

  1. 繼承 SQLErrorCodeSQLExceptionTranslator,重寫 customTranslate
  2. 繼承 SQLExceptionTranslator,重寫 translate,然後在 sql-error-codes.xml注入。
  3. 使用 SQLErrorCodes#customTranslations ,然後在 sql-error-codes.xml 配置相關錯誤碼轉換的規則。

第三種方式改動最小,比較簡單。首先在 classpath 下生成 sql-error-codes.xml,複製原有配置,最後配置 customTranslations

customTranslations

這裡需要注意的是,需要轉化的異常型別必須為 DataAccessException 子類。下面面我們自定義一個異常。

自定義異常

總結

Spirng 異常處理將 SQL 異常轉化成內建異常,遮蔽不同資料庫返回碼不一致的帶來的問題。

最後總結本文的知識點,希望幫助到大家。

知識圖譜

幫助

Handling SQLExceptions

相關文章