Spring轉(zhuǎn)換SQLException并埋入擴(kuò)展點(diǎn)的工作過(guò)程

本篇內(nèi)容介紹了“Spring轉(zhuǎn)換SQLException并埋入擴(kuò)展點(diǎn)的工作過(guò)程”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(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)頁(yè)設(shè)計(jì)與制作、羅江企業(yè)網(wǎng)站模板建站服務(wù),10年羅江做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。

SQLException 中字段 SQLState 和 vendorCode

我不知道這樣理解是否正確:

SQLState 的來(lái)源是對(duì) SQL 標(biāo)準(zhǔn)的履約。盡管 SQL 數(shù)據(jù)庫(kù)的廠商有很多,只要它們都尊重 SQL 標(biāo)準(zhǔn)和 JDBC 的規(guī)范,那么不同廠商在同樣的錯(cuò)誤上必然返回同樣的 SQLState。

vendorCode 很魔幻,它對(duì)應(yīng)的 getter 方法名為 getErrorCode。但字段名里的 vendor 和它的 getter 方法上的注解已經(jīng)將出賣了它的意義。vendorCode 或者說(shuō) errorCode 并不是 SQL 標(biāo)準(zhǔn)的內(nèi)容,不同數(shù)據(jù)庫(kù)廠商可能對(duì)同樣的錯(cuò)誤返回不同的 errorCode。

SQLErrorCodeSQLExceptionTranslator 是如何注冊(cè)為 Bean 的

我本來(lái)猜測(cè)默認(rèn)的轉(zhuǎn)換器 org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator 在 SpringBoot 環(huán)境下,在引入 spring-boot-starter-jdbc 依賴后會(huì)在某個(gè) *AutoConfiguration 中被一個(gè)帶有 @Bean 的方法注冊(cè),這個(gè)方法同時(shí)可能還將也注冊(cè)為 Bean 的 SQLErrorCodes 通過(guò) setter 注入。

然而后者,即默認(rèn)的 SQLErrorCodes 的注冊(cè)為 Bean 的地方我找到了, org\springframework\jdbc\support\sql-error-codes.xml,在這個(gè)里面注冊(cè)并注入了一系列屬性的。然而我對(duì) SQLErrorCodeSQLExceptionTranslator 和它的父類借助IDEA的 Alt + F7 檢索被使用的地方并沒發(fā)現(xiàn)上一段說(shuō)的注冊(cè)它為 Bean 的方法。如果有老哥知道它在哪兒成為 Bean 加入上下文環(huán)境的告訴我一聲。我先把假設(shè)當(dāng)事實(shí)用著了。

批處理產(chǎn)生的異常只關(guān)注尾部嗎

請(qǐng)看正文的第一段。

正文

org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator,這是 Spring 默認(rèn)提供的轉(zhuǎn)換各種 SQLException 異常為Spring 內(nèi)置的統(tǒng)一的異常類型的轉(zhuǎn)換器。本文主要通過(guò)它,準(zhǔn)確地說(shuō)是它的 protected DataAccessException doTranslate(String task, @Nullable String sql, SQLException ex) 方法來(lái)看一看它整個(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 方法入門第一段,它做的事情很簡(jiǎn)單,起手先判斷這個(gè)異常是不是 BatchUpdateException 類型的從名字上猜,這個(gè)異常只會(huì)在批處理時(shí)拋出。而之后的sqlEx.getNextException(),我們追進(jìn)源碼去看,有這些重要相關(guān)代碼:

    /**
     * Retrieves the exception chained to this
     * <code>SQLException</code> object by setNextException(SQLException ex).
     *
     * @return the next <code>SQLException</code> object in the chain;
     *         <code>null</code> if there are none
     * @see #setNextException
     */
    public SQLException getNextException() {
        return (next);
    }


    /**
     * Adds an <code>SQLException</code> object to the end of the chain.
     *
     * @param ex the new exception that will be added to the end of
     *            the <code>SQLException</code> 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,如果通過(guò),則 sqlEx 就置為這個(gè)最末尾異常。而閱讀之后的代碼后能知道,sqlEx 就是會(huì)被 Spring 轉(zhuǎn)換處理的目標(biāo)。

也就是說(shuō)只要末尾的SQLException是個(gè)正常的異常,Spring 就只關(guān)心末尾的異常了。這是為什么?

另外,為什么 SQLException 要搞一個(gè)異常鏈呢?頂層 Exception 不是已經(jīng)設(shè)置了 cause 這樣一個(gè)機(jī)制來(lái)實(shí)現(xiàn)異常套娃嗎?

第二段 第一個(gè)擴(kuò)展點(diǎ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)過(guò)去看下它的代碼:

	@Nullable
	protected DataAccessException customTranslate(String task, @Nullable String sql, SQLException sqlEx) {
		return null;
	}

第一個(gè)埋入的擴(kuò)展點(diǎn)出現(xiàn)了。這個(gè)方法的存在,使得我們可以通過(guò)繼承 SQLErrorCodeSQLExceptionTranslator 類進(jìn)行擴(kuò)展,裝入自己的私活。我猜測(cè)注冊(cè) SQLErrorCodeSQLExceptionTranslator 到環(huán)境中的那個(gè) Bean 方法應(yīng)該是有 @ConditionalOnMissingBean 在的,我們手動(dòng)將自己實(shí)現(xiàn)的繼承 SQLErrorCodeSQLExceptionTranslator 的類注冊(cè)為 Bean 后它自己就不會(huì)再注冊(cè)了,從而實(shí)現(xiàn)偷換轉(zhuǎn)換器夾帶私活。

第三段 第二和第三個(gè)擴(kuò)展點(diǎ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 注冊(cè)的 Bean 的類。而在那個(gè) xml 文件的開頭,人家明確說(shuō)了:

- 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 注冊(cè)為 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)類注冊(cè)為 Bean,并在復(fù)制過(guò)來(lái)的注冊(cè) 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)類然后注冊(cè)為 Bean,取代默認(rèn)的。那SQLErrorCodes` 都徹底換了個(gè),夾帶私活肯定沒問題了。

第四段 準(zhǔn)備工作

第四段也是最后一段,這段比較長(zhǎng),段內(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 方法的注釋我們可以推測(cè),isUseSqlStateForTranslation() 返回 true 時(shí),數(shù)據(jù)庫(kù)廠商是那種 JDBC 執(zhí)行出錯(cuò)不返回 errorCode 只返回 SQLState 的,兩者都返回的這里應(yīng)該返回 false (兩者都不返回的那不是正常的 JDBC 實(shí)現(xiàn))。在此基礎(chǔ)上繼續(xù)回去理解代碼,那這里就是獲取待轉(zhuǎn)化的異常中最有價(jià)值的可以表明自己錯(cuò)誤的異常的對(duì)應(yīng)的錯(cuò)誤碼置給 errorCode。而后續(xù)操作都基于這個(gè) errorCode

第四段 第四個(gè)擴(kuò)展點(diǎn)

			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.

回到第四段對(duì) CustomSQLErrorCodesTranslation[] 的使用:

					for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) {
						if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0 &&
								customTranslation.getExceptionClass() != null)

對(duì) CustomSQLErrorCodesTranslation[] 中的每一個(gè) customTranslations,匹配當(dāng)前的 errorCode 是不是在它的處理范圍內(nèi)(即它的 private String[] errorCodes 這個(gè)數(shù)組類型的成員中有和 errorCode 相同的值),如果有,且它 private Class<?> exceptionClass 成員不為 null,就說(shuō)明該將當(dāng)前異常轉(zhuǎn)化為 exceptionClass 類的異常。

話說(shuō)我一開始寫這篇文章的動(dòng)機(jī),就是極客時(shí)間-玩轉(zhuǎn)Spring全家桶-了解Spring的JDBC抽象異常里講,而我想具體搞明白。而課程視頻就是在自定義的``sql-error-codes.xml中注入自定義的CustomSQLErrorCodesTranslation[]`來(lái)完成擴(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() 雖說(shuō)也能通過(guò)自定義的 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 中對(duì)應(yīng) MySQL 數(shù)據(jù)庫(kù)的內(nèi)容:

	<bean id="MySQL" class="org.springframework.jdbc.support.SQLErrorCodes">
		<property name="databaseProductNames">
			<list>
				<value>MySQL</value>
				<value>MariaDB</value>
			</list>
		</property>
		<property name="badSqlGrammarCodes">
			<value>1054,1064,1146</value>
		</property>
		<property name="duplicateKeyCodes">
			<value>1062</value>
		</property>
		<property name="dataIntegrityViolationCodes">
			<value>630,839,840,893,1169,1215,1216,1217,1364,1451,1452,1557</value>
		</property>
		<property name="dataAccessResourceFailureCodes">
			<value>1</value>
		</property>
		<property name="cannotAcquireLockCodes">
			<value>1205,3572</value>
		</property>
		<property name="deadlockLoserCodes">
			<value>1213</value>
		</property>
	</bean>

后面還有一些代碼就是 Spring 處理不過(guò)來(lái)的 SQLException 做一些日志記錄,不值得多說(shuō)了。

擴(kuò)展點(diǎn)總結(jié)

第一個(gè)擴(kuò)展點(diǎn)是 protected 的方法,用戶可以通過(guò)繼承后在此方法中添加自己的實(shí)現(xiàn)的實(shí)現(xiàn)后將自己的實(shí)現(xiàn)類注冊(cè)為 Bean,再結(jié)合 Spring 為默認(rèn)的實(shí)現(xiàn)注冊(cè) Bean 時(shí)的 @ConditionalOnMissingBean 限制,達(dá)成了擴(kuò)展點(diǎn)。

隨后的幾個(gè)擴(kuò)展點(diǎn)的本質(zhì)都是為默認(rèn)實(shí)現(xiàn)的 Bean 中留下可注入的成員,用戶通過(guò)實(shí)現(xiàn)特定接口并將其注冊(cè)為 Bean,結(jié)合成員注入將帶著自己實(shí)現(xiàn)邏輯的 Bean 注入后將自己的私活帶入。

“Spring轉(zhuǎn)換SQLException并埋入擴(kuò)展點(diǎn)的工作過(guò)程”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

網(wǎng)站題目:Spring轉(zhuǎn)換SQLException并埋入擴(kuò)展點(diǎn)的工作過(guò)程
分享鏈接:http://bm7419.com/article26/pcspjg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化營(yíng)銷型網(wǎng)站建設(shè)、定制網(wǎng)站、自適應(yīng)網(wǎng)站做網(wǎng)站、網(wǎng)站維護(hù)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名