本篇內(nèi)容介紹了“Spring轉(zhuǎn)換SQLException并埋入擴(kuò)展點(diǎn)的工作過程”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供羅江網(wǎng)站建設(shè)、羅江做網(wǎng)站、羅江網(wǎng)站設(shè)計(jì)、羅江網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、羅江企業(yè)網(wǎng)站模板建站服務(wù),10年羅江做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
我不知道這樣理解是否正確:
SQLState 的來源是對 SQL 標(biāo)準(zhǔn)的履約。盡管 SQL 數(shù)據(jù)庫的廠商有很多,只要它們都尊重 SQL 標(biāo)準(zhǔn)和 JDBC 的規(guī)范,那么不同廠商在同樣的錯(cuò)誤上必然返回同樣的 SQLState。
vendorCode 很魔幻,它對應(yīng)的 getter 方法名為 getErrorCode。但字段名里的 vendor 和它的 getter 方法上的注解已經(jīng)將出賣了它的意義。vendorCode 或者說 errorCode 并不是 SQL 標(biāo)準(zhǔn)的內(nèi)容,不同數(shù)據(jù)庫廠商可能對同樣的錯(cuò)誤返回不同的 errorCode。
我本來猜測默認(rèn)的轉(zhuǎn)換器 org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
在 SpringBoot 環(huán)境下,在引入 spring-boot-starter-jdbc
依賴后會(huì)在某個(gè) *AutoConfiguration 中被一個(gè)帶有 @Bean
的方法注冊,這個(gè)方法同時(shí)可能還將也注冊為 Bean 的 SQLErrorCodes
通過 setter 注入。
然而后者,即默認(rèn)的 SQLErrorCodes
的注冊為 Bean 的地方我找到了, org\springframework\jdbc\support\sql-error-codes.xml
,在這個(gè)里面注冊并注入了一系列屬性的。然而我對 SQLErrorCodeSQLExceptionTranslator
和它的父類借助IDEA的 Alt + F7
檢索被使用的地方并沒發(fā)現(xiàn)上一段說的注冊它為 Bean 的方法。如果有老哥知道它在哪兒成為 Bean 加入上下文環(huán)境的告訴我一聲。我先把假設(shè)當(dāng)事實(shí)用著了。
請看正文的第一段。
org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
,這是 Spring 默認(rèn)提供的轉(zhuǎn)換各種 SQLException 異常為Spring 內(nèi)置的統(tǒng)一的異常類型的轉(zhuǎn)換器。本文主要通過它,準(zhǔn)確地說是它的 protected DataAccessException doTranslate(String task, @Nullable String sql, SQLException ex)
方法來看一看它整個(gè)的處理思路以及其中給用戶流出的擴(kuò)展點(diǎn)。
SQLException sqlEx = ex; if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null) { SQLException nestedSqlEx = sqlEx.getNextException(); if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null) { sqlEx = nestedSqlEx; } }
上面是 doTranslate 方法入門第一段,它做的事情很簡單,起手先判斷這個(gè)異常是不是 BatchUpdateException
類型的從名字上猜,這個(gè)異常只會(huì)在批處理時(shí)拋出。而之后的sqlEx.getNextException()
,我們追進(jìn)源碼去看,有這些重要相關(guān)代碼:
/** * Retrieves the exception chained to this *SQLException
object by setNextException(SQLException ex). * * @return the nextSQLException
object in the chain; *null
if there are none * @see #setNextException */ public SQLException getNextException() { return (next); } /** * Adds anSQLException
object to the end of the chain. * * @param ex the new exception that will be added to the end of * theSQLException
chain * @see #getNextException */ public void setNextException(SQLException ex) { SQLException current = this; for(;;) { SQLException next=current.next; if (next != null) { current = next; continue; } if (nextUpdater.compareAndSet(current,null,ex)) { return; } current=current.next; } }
一閱讀 setNextException
就知道,玩鏈表的老手了,next 一定永遠(yuǎn)指向鏈表最末元素。
那在看回轉(zhuǎn)換器的代碼,那么它做的事情就是:取出異常鏈的最末尾異常,然后判斷 nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null
,如果通過,則 sqlEx
就置為這個(gè)最末尾異常。而閱讀之后的代碼后能知道,sqlEx
就是會(huì)被 Spring 轉(zhuǎn)換處理的目標(biāo)。
也就是說只要末尾的SQLException是個(gè)正常的異常,Spring 就只關(guān)心末尾的異常了。這是為什么?
另外,為什么 SQLException 要搞一個(gè)異常鏈呢?頂層 Exception 不是已經(jīng)設(shè)置了 cause 這樣一個(gè)機(jī)制來實(shí)現(xiàn)異常套娃嗎?
// First, try custom translation from overridden method. DataAccessException dae = customTranslate(task, sql, sqlEx); if (dae != null) { return dae; }
customTranslate
這個(gè)方法光聽名字就很有擴(kuò)展點(diǎn)的感覺,跳轉(zhuǎn)過去看下它的代碼:
@Nullable protected DataAccessException customTranslate(String task, @Nullable String sql, SQLException sqlEx) { return null; }
第一個(gè)埋入的擴(kuò)展點(diǎn)出現(xiàn)了。這個(gè)方法的存在,使得我們可以通過繼承 SQLErrorCodeSQLExceptionTranslator
類進(jìn)行擴(kuò)展,裝入自己的私活。我猜測注冊 SQLErrorCodeSQLExceptionTranslator
到環(huán)境中的那個(gè) Bean 方法應(yīng)該是有 @ConditionalOnMissingBean
在的,我們手動(dòng)將自己實(shí)現(xiàn)的繼承 SQLErrorCodeSQLExceptionTranslator
的類注冊為 Bean 后它自己就不會(huì)再注冊了,從而實(shí)現(xiàn)偷換轉(zhuǎn)換器夾帶私活。
// Next, try the custom SQLException translator, if available. SQLErrorCodes sqlErrorCodes = getSqlErrorCodes(); if (sqlErrorCodes != null) { SQLExceptionTranslator customTranslator = sqlErrorCodes.getCustomSqlExceptionTranslator(); if (customTranslator != null) { DataAccessException customDex = customTranslator.translate(task, sql, sqlEx); if (customDex != null) { return customDex; } } }
SQLErrorCodes
就是之前在 org\springframework\jdbc\support\sql-error-codes.xml
注冊的 Bean 的類。而在那個(gè) xml 文件的開頭,人家明確說了:
- Default SQL error codes for well-known databases. - Can be overridden by definitions in a "sql-error-codes.xml" file - in the root of the class path.
如果你在自己的項(xiàng)目的 class path 下寫一個(gè) sql-error-codes.xml
,那么 Spring 默認(rèn)提供的就會(huì)被覆蓋。
注意 sqlErrorCodes.getCustomSqlExceptionTranslator()
。這一步從一個(gè) Bean 中取出它的一個(gè)成員,而這個(gè)成員是完全有 getter 顯然也有 setter,也就意味著它可以在 SQLErrorCodes
注冊為 Bean 的同時(shí)的一個(gè)成員 Bean 注入。
結(jié)合 sql-error-codes.xml
頭部的注釋,于是有:用戶先寫一個(gè)夾帶自己私活的 SQLExceptionTranslator
接口的實(shí)現(xiàn)類。然后自己在項(xiàng)目 class path 下新建一個(gè) sql-error-codes.xml
,復(fù)制 Spring 已經(jīng)提供的內(nèi)容。再然后,在 xml 里將自己的私活 SQLExceptionTranslator
實(shí)現(xiàn)類注冊為 Bean,并在復(fù)制過來的注冊 SQLErrorCodes
為 Bean 的內(nèi)容里添加一條 customSqlExceptionTranslator
的成員的注入,用的當(dāng)然是自己的那個(gè)私活 Bean。這樣就完成了擴(kuò)展。
其實(shí)這里還有第三個(gè)擴(kuò)展點(diǎn)。注意第一行的 getSqlErrorCodes();
,有這個(gè) getter 也有 setter 還有 sqlErrorCodes
的成員,這里 SQLErrorCodes
也是后注入給 ``SQLErrorCodeSQLExceptionTranslator的組件。那我們也可以自定義一個(gè)
SQLErrorCodes的實(shí)現(xiàn)類然后注冊為 Bean,取代默認(rèn)的。那
SQLErrorCodes` 都徹底換了個(gè),夾帶私活肯定沒問題了。
第四段也是最后一段,這段比較長,段內(nèi)部還需分片看。
// Check SQLErrorCodes with corresponding error code, if available. if (sqlErrorCodes != null) { String errorCode; if (sqlErrorCodes.isUseSqlStateForTranslation()) { errorCode = sqlEx.getSQLState(); } else { // Try to find SQLException with actual error code, looping through the causes. // E.g. applicable to java.sql.DataTruncation as of JDK 1.6. SQLException current = sqlEx; while (current.getErrorCode() == 0 && current.getCause() instanceof SQLException) { current = (SQLException) current.getCause(); } errorCode = Integer.toString(current.getErrorCode()); }
這一段是做了一些準(zhǔn)備工作。
/** * Set this property to true for databases that do not provide an error code * but that do provide SQL State (this includes PostgreSQL). */ public void setUseSqlStateForTranslation(boolean useStateCodeForTranslation) { this.useSqlStateForTranslation = useStateCodeForTranslation; } public boolean isUseSqlStateForTranslation() { return this.useSqlStateForTranslation; }
從 setUseSqlStateForTranslation
方法的注釋我們可以推測,isUseSqlStateForTranslation()
返回 true
時(shí),數(shù)據(jù)庫廠商是那種 JDBC 執(zhí)行出錯(cuò)不返回 errorCode 只返回 SQLState 的,兩者都返回的這里應(yīng)該返回 false
(兩者都不返回的那不是正常的 JDBC 實(shí)現(xiàn))。在此基礎(chǔ)上繼續(xù)回去理解代碼,那這里就是獲取待轉(zhuǎn)化的異常中最有價(jià)值的可以表明自己錯(cuò)誤的異常的對應(yīng)的錯(cuò)誤碼置給 errorCode
。而后續(xù)操作都基于這個(gè) errorCode
。
if (errorCode != null) { // Look for defined custom translations first. CustomSQLErrorCodesTranslation[] customTranslations = sqlErrorCodes.getCustomTranslations(); if (customTranslations != null) { for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) { if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0 && customTranslation.getExceptionClass() != null) { DataAccessException customException = createCustomException( task, sql, sqlEx, customTranslation.getExceptionClass()); if (customException != null) { logTranslation(task, sql, sqlEx, true); return customException; } } } }
這個(gè)第四個(gè)擴(kuò)展點(diǎn),就是可以注入到 SQLErrorCodes
中的 CustomSQLErrorCodesTranslation[]
,細(xì)看 CustomSQLErrorCodesTranslation
這個(gè)類:
/** * JavaBean for holding custom JDBC error codes translation for a particular * database. The "exceptionClass" property defines which exception will be * thrown for the list of error codes specified in the errorCodes property. * * @author Thomas Risberg * @since 1.1 * @see SQLErrorCodeSQLExceptionTranslator */ public class CustomSQLErrorCodesTranslation { private String[] errorCodes = new String[0]; @Nullable private Class> exceptionClass;
我沒復(fù)制粘貼完,因?yàn)檫@些已經(jīng)夠了,看注釋:
The "exceptionClass" property defines which exception will be thrown for the list of error codes specified in the errorCodes property.
回到第四段對 CustomSQLErrorCodesTranslation[]
的使用:
for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) { if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0 && customTranslation.getExceptionClass() != null)
對 CustomSQLErrorCodesTranslation[]
中的每一個(gè) customTranslations
,匹配當(dāng)前的 errorCode
是不是在它的處理范圍內(nèi)(即它的 private String[] errorCodes
這個(gè)數(shù)組類型的成員中有和 errorCode
相同的值),如果有,且它 private Class> exceptionClass
成員不為 null
,就說明該將當(dāng)前異常轉(zhuǎn)化為 exceptionClass
類的異常。
話說我一開始寫這篇文章的動(dòng)機(jī),就是極客時(shí)間-玩轉(zhuǎn)Spring全家桶-了解Spring的JDBC抽象異常里講,而我想具體搞明白。而課程視頻就是在自定義的``sql-error-codes.xml中注入自定義的
CustomSQLErrorCodesTranslation[]`來完成擴(kuò)展的。
// Next, look for grouped error codes. if (Arrays.binarySearch(sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx); } else if (Arrays.binarySearch(sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new InvalidResultSetAccessException(task, (sql != null ? sql : ""), sqlEx); } //后面還有大同小異邏輯一致的幾個(gè)就不復(fù)制粘貼了
這一段的大體邏輯和上一節(jié)相同,已經(jīng)到了最后,沒有了擴(kuò)展空間。
getBadSqlGrammarCodes()
,getInvalidResultSetAccessCodes()
雖說也能通過自定義的 sql-error-codes.xml
去改,但沒必要了,因?yàn)樗鼈兎祷氐漠惓5念愋投际菍懰赖?,這塊地方其實(shí)是 Spring 給經(jīng)典的 SQL 錯(cuò)誤的自留地,我們就不要?jiǎng)恿?。寫自己?sql-error-codes.xml
時(shí)復(fù)制粘貼下這塊東西就好,如下是 Spring 原生的 sql-error-codes.xml
中對應(yīng) MySQL 數(shù)據(jù)庫的內(nèi)容:
MySQL MariaDB 1054,1064,1146 1062 630,839,840,893,1169,1215,1216,1217,1364,1451,1452,1557 1 1205,3572 1213
后面還有一些代碼就是 Spring 處理不過來的 SQLException 做一些日志記錄,不值得多說了。
第一個(gè)擴(kuò)展點(diǎn)是 protected
的方法,用戶可以通過繼承后在此方法中添加自己的實(shí)現(xiàn)的實(shí)現(xiàn)后將自己的實(shí)現(xiàn)類注冊為 Bean,再結(jié)合 Spring 為默認(rèn)的實(shí)現(xiàn)注冊 Bean 時(shí)的 @ConditionalOnMissingBean
限制,達(dá)成了擴(kuò)展點(diǎn)。
隨后的幾個(gè)擴(kuò)展點(diǎn)的本質(zhì)都是為默認(rèn)實(shí)現(xiàn)的 Bean 中留下可注入的成員,用戶通過實(shí)現(xiàn)特定接口并將其注冊為 Bean,結(jié)合成員注入將帶著自己實(shí)現(xiàn)邏輯的 Bean 注入后將自己的私活帶入。
“Spring轉(zhuǎn)換SQLException并埋入擴(kuò)展點(diǎn)的工作過程”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!