Spring事務(wù)捕獲異常后依舊回滾的解決
前沿
一段生產(chǎn)事故發(fā)人深省,在Spring的聲明式事務(wù)中手動(dòng)捕獲異常,居然判定回滾了,這是什么操作?話不多說直接上代碼
@Service public class A { ? ? @Autowired ? ? private B b; ? ? @Autowired ? ? private C c; ? ? @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT) ? ? public void operate() { ? ? ? ? try { ? ? ? ? ? ? b.insertB(); ? ? ? ? ? ? c.insertC(); ? ? ? ? }catch (Exception e) { ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? } ? ? } } @Service public class B { ? ? @Autowired ? ? private BM bm; ? ? @Transactional(propagation = Propagation.REQUIRED) ? ? public int insertB() { ? ? ? ? return bm.insert("B"); ? ? } } @Service public class C { ? ? @Autowired ? ? private CM cm; ? ? @Transactional(propagation = Propagation.REQUIRED) ? ? public int insertC() { ? ? ? ? return cm.insert("C"); ? ? } }
問題闡述
好了大家都看到上面這段代碼了,在正常的情況的我們會(huì)往B表和C表中各插入一條數(shù)據(jù),那么當(dāng)代碼出現(xiàn)異常時(shí)又會(huì)怎么樣呢?
我們現(xiàn)在假設(shè)B插入數(shù)據(jù)成功,但是C插入數(shù)據(jù)失敗了,此時(shí)異常會(huì)上拋到A,被A中operate方法的try - cache所捕獲,正常來說此時(shí)數(shù)據(jù)庫中B能插入一條記錄,而C表插入失敗,這是我們期望的情況,但事實(shí)卻不是,實(shí)際情況是B表沒有插入數(shù)據(jù),C表也沒有插入數(shù)據(jù),也就是說整個(gè)操作被Spring給回滾了
注意點(diǎn)
如果代碼稍稍變動(dòng)一下,將try - cache放在insertC的代碼塊中,在同樣的場景下,B中會(huì)成功插入一條記錄
知識(shí)點(diǎn)前置條件
了解Spring的傳播機(jī)制的可以直接跳過
我們先要搞清楚Spring中的REQUIRED的作用
REQUIRED:如果當(dāng)前沒有事務(wù)就創(chuàng)建一個(gè)新的事務(wù),如果當(dāng)前已經(jīng)存在事務(wù)就加入到當(dāng)前事務(wù)
也就是說當(dāng)我們的傳播機(jī)制同時(shí)為REQUIRED時(shí),A、B、C三者的事務(wù)是共用一個(gè)的,只有當(dāng)A的流程全部走完時(shí)才會(huì)做一次commit或者rollback操作,不會(huì)在執(zhí)行B或者C的過程中進(jìn)行commit和rollback
問題追蹤
好,有了一定的知識(shí)儲(chǔ)備,我們一起來看源碼
我們首先找到Spring事務(wù)的代理入口TransactionInterceptor, 當(dāng)我們通過調(diào)用A類中的operate方法時(shí)會(huì)調(diào)用TransactionInterceptor的invoke方法,這是整個(gè)事務(wù)的入口,我們直接看重點(diǎn)invoke中的invokeWithinTransaction方法
//獲取事務(wù)屬性類 AnnotationTransactionAttributeSource TransactionAttributeSource tas = getTransactionAttributeSource(); //獲取事務(wù)屬性 final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); //獲取事務(wù)管理器 final TransactionManager tm = determineTransactionManager(txAttr); PlatformTransactionManager ptm = asPlatformTransactionManager(tm); //獲取joinpoint final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); //注解事務(wù)會(huì)走這里 if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) { ?? ?// Standard transaction demarcation with getTransaction and commit/rollback calls. ?? ?//開啟事務(wù) ?? ?TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); ?? ?Object retVal; ?? ?try { ?? ??? ?// This is an around advice: Invoke the next interceptor in the chain. ?? ??? ?// This will normally result in a target object being invoked. ?? ??? ?retVal = invocation.proceedWithInvocation(); ?? ?} catch (Throwable ex) { ?? ??? ?// target invocation exception ?? ??? ?//事務(wù)回滾 ?? ??? ?completeTransactionAfterThrowing(txInfo, ex); ?? ??? ?throw ex; ?? ?} finally { ?? ??? ?cleanupTransactionInfo(txInfo); ?? ?} ?? ?if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) { ?? ??? ?// Set rollback-only in case of Vavr failure matching our rollback rules... ?? ??? ?TransactionStatus status = txInfo.getTransactionStatus(); ?? ??? ?if (status != null && txAttr != null) { ?? ??? ??? ?retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status); ?? ??? ?} ?? ?} ?? ?//事務(wù)提交 ?? ?commitTransactionAfterReturning(txInfo); ?? ?return retVal; }
不重要的代碼我已經(jīng)省略了,好我們來看這個(gè)流程,上面這段代碼很明顯反應(yīng)出了,當(dāng)我們程序執(zhí)行過程中拋出了異常時(shí)會(huì)調(diào)用到completeTransactionAfterThrowing的回滾操作,如果沒有發(fā)生異常最終會(huì)調(diào)用事務(wù)提交commitTransactionAfterReturning, 我們來分析一下
正常情況是C發(fā)生異常,并且執(zhí)行到了completeTransactionAfterThrowing事務(wù)回滾,但是因?yàn)椴皇切聞?chuàng)建的事務(wù),而是加入的事務(wù)所以并不會(huì)觸發(fā)回滾操作,而在A中捕獲了該異常,并且最終走到commitTransactionAfterReturning事務(wù)提交,事實(shí)是這樣的嗎?
事實(shí)上就是這樣的,那就奇怪了,我明明提交了,怎么反而回滾了,我們繼續(xù)往下看
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException { ?? ?// Use defaults if no transaction definition given. ?? ?TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults()); ?? ?//重點(diǎn)看.. DataSourceTransactionObject拿到對象 ?? ?Object transaction = doGetTransaction(); ?? ?boolean debugEnabled = logger.isDebugEnabled(); ?? ?//第一次進(jìn)來connectionHolder為空的, 所以不存在事務(wù) ?? ?if (isExistingTransaction(transaction)) { ?? ??? ?// Existing transaction found -> check propagation behavior to find out how to behave. ?? ??? ?//如果不是第一次進(jìn)來, 則會(huì)走這個(gè)邏輯 ?? ??? ?return handleExistingTransaction(def, transaction, debugEnabled); ?? ?} ?? ?// Check definition settings for new transaction. ?? ?if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { ?? ??? ?throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout()); ?? ?} ?? ?// No existing transaction found -> check propagation behavior to find out how to proceed. ?? ?if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { ?? ??? ?throw new IllegalTransactionStateException( ?? ??? ??? ??? ?"No existing transaction found for transaction marked with propagation 'mandatory'"); ?? ?} ?? ?//第一次進(jìn)來大部分會(huì)走這里(傳播屬性是 Required | Requested New | Nested) ?? ?else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || ?? ??? ??? ?def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || ?? ??? ??? ?def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { ?? ??? ?//先掛起 ?? ??? ?SuspendedResourcesHolder suspendedResources = suspend(null); ?? ??? ?if (debugEnabled) { ?? ??? ??? ?logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def); ?? ??? ?} ?? ??? ?try { ?? ??? ??? ?//開啟事務(wù) ?? ??? ??? ?return startTransaction(def, transaction, debugEnabled, suspendedResources); ?? ??? ?} catch (RuntimeException | Error ex) { ?? ??? ??? ?resume(null, suspendedResources); ?? ??? ??? ?throw ex; ?? ??? ?} ?? ?} else { ?? ??? ?// Create "empty" transaction: no actual transaction, but potentially synchronization. ?? ??? ?if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) { ?? ??? ??? ?logger.warn("Custom isolation level specified but no actual transaction initiated; " + ?? ??? ??? ??? ??? ?"isolation level will effectively be ignored: " + def); ?? ??? ?} ?? ??? ?boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); ?? ??? ?return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null); ?? ?} }
這段代碼是開啟事務(wù)的代碼,我們來看,當(dāng)我們A第一次走進(jìn)來的時(shí)候,此時(shí)是沒有事務(wù)的,所以isExistingTransaction方法不成立,往下走,因?yàn)槲覀兊膫鞑C(jī)制是REQUIRED,所以我們會(huì)走到startTransaction方法中
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) { ?? ?boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); ?? ?//創(chuàng)建一個(gè)新的事務(wù)狀態(tài), 注意這里的newTransaction 屬性為true ?? ?DefaultTransactionStatus status = newTransactionStatus( ?? ??? ??? ?definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); ?? ?//開啟事務(wù) ?? ?doBegin(transaction, definition); ?? ?//開啟事務(wù)后, 改變事務(wù)狀態(tài) ?? ?prepareSynchronization(status, definition); ?? ?return status; }
好這里我們只需要關(guān)注一個(gè)點(diǎn)那就是newTransactionStatus的第三個(gè)參數(shù)newTransaction,只有當(dāng)我們新創(chuàng)建一個(gè)事務(wù)的時(shí)候才會(huì)為true,這個(gè)屬性很重要,我們后續(xù)還會(huì)用到它
好了,到這里第一次的事務(wù)開啟就已經(jīng)完成了,然后我們會(huì)調(diào)用業(yè)務(wù)邏輯,當(dāng)調(diào)用insertB時(shí),又會(huì)走到getTransaction,我們繼續(xù)來看它,此時(shí)isExistingTransaction就可以拿到值了,因?yàn)锳已經(jīng)幫我們創(chuàng)建好了事務(wù),此時(shí)會(huì)調(diào)用到handleExistingTransaction方法
//如果第二次進(jìn)來還是PROPAFGATION_REQUIRED, 走這里, newTransation為false return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
針對REQUIRED有用的代碼就這一句,其他全部不用看,同樣的我們看到第三個(gè)參數(shù)newTransaction,這里是false了,說明是加入了之前的事務(wù),而不是自己新創(chuàng)建的,然后執(zhí)行業(yè)務(wù)代碼,最后走到commit,我們來看看commit中做了什么
//如果有回滾點(diǎn) if (status.hasSavepoint()) { ?? ?if (status.isDebug()) { ?? ??? ?logger.debug("Releasing transaction savepoint"); ?? ?} ?? ?unexpectedRollback = status.isGlobalRollbackOnly(); ?? ?status.releaseHeldSavepoint(); } //如果是新事務(wù), 則提交事務(wù) else if (status.isNewTransaction()) { ?? ?if (status.isDebug()) { ?? ??? ?logger.debug("Initiating transaction commit"); ?? ?} ?? ?unexpectedRollback = status.isGlobalRollbackOnly(); ?? ?doCommit(status); } else if (isFailEarlyOnGlobalRollbackOnly()) { ?? ?unexpectedRollback = status.isGlobalRollbackOnly(); }
它什么事情都沒有做,為什么?因?yàn)槲覀兊膎ewTransaction不為true,所以當(dāng)我們的代碼在operate方法全部執(zhí)行完以后才會(huì)走到這里
好接下來我們來看insertC,前面的流程都一模一樣,我們直接看到回滾代碼
private void processRollback(DefaultTransactionStatus status, boolean unexpected) { ?? ?try { ?? ??? ?boolean unexpectedRollback = unexpected; ?? ??? ?try { ?? ??? ??? ?triggerBeforeCompletion(status); ?? ??? ??? ?if (status.hasSavepoint()) { ?? ??? ??? ??? ?if (status.isDebug()) { ?? ??? ??? ??? ??? ?logger.debug("Rolling back transaction to savepoint"); ?? ??? ??? ??? ?} ?? ??? ??? ??? ?status.rollbackToHeldSavepoint(); ?? ??? ??? ?} else if (status.isNewTransaction()) { ? ? ? ? if (status.isDebug()) { ?? ??? ??? ??? ??? ?logger.debug("Initiating transaction rollback"); ?? ??? ??? ??? ?} ?? ??? ??? ??? ?doRollback(status); ?? ??? ??? ?} else { ?? ??? ??? ??? ?// Participating in larger transaction ?? ??? ??? ??? ?if (status.hasTransaction()) { ?? ??? ??? ??? ??? ?if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { ?? ??? ??? ??? ??? ??? ?if (status.isDebug()) { ?? ??? ??? ??? ??? ??? ??? ?logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); ?? ??? ??? ??? ??? ??? ?} ?? ??? ??? ??? ??? ??? ?doSetRollbackOnly(status); ?? ??? ??? ??? ??? ?} else { ?? ??? ??? ??? ??? ??? ?if (status.isDebug()) { ?? ??? ??? ??? ??? ??? ??? ?logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); ?? ??? ??? ??? ??? ??? ?} ?? ??? ??? ??? ??? ?} ?? ??? ??? ??? ?} else { ?? ??? ??? ??? ??? ?logger.debug("Should roll back transaction but cannot - no transaction available"); ?? ??? ??? ??? ?} ?? ??? ??? ??? ?// Unexpected rollback only matters here if we're asked to fail early ?? ??? ??? ??? ?if (!isFailEarlyOnGlobalRollbackOnly()) { ?? ??? ??? ??? ??? ?unexpectedRollback = false; ?? ??? ??? ??? ?} ?? ??? ??? ?} ?? ??? ?} catch (RuntimeException | Error ex) { ?? ??? ??? ?triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); ?? ??? ??? ?throw ex; ?? ??? ?} ?? ??? ?triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); ?? ??? ?// Raise UnexpectedRollbackException if we had a global rollback-only marker ?? ??? ?if (unexpectedRollback) { ?? ??? ??? ?throw new UnexpectedRollbackException( ?? ??? ??? ??? ??? ?"Transaction rolled back because it has been marked as rollback-only"); ?? ??? ?} ?? ?} finally { ?? ??? ?cleanupAfterCompletion(status); ?? ?} }
我們的insertC方法同樣它的newTransaction不是true,所以最終會(huì)走到doSetRollbackOnly,這個(gè)方法重中之重,最后會(huì)調(diào)用這樣一段代碼
public void setRollbackOnly() { ?? ?this.rollbackOnly = true; }
然后我們就要執(zhí)行到我們的關(guān)鍵代碼A中的operate的提交代碼了
public final void commit(TransactionStatus status) throws TransactionException { ?? ?if (status.isCompleted()) { ?? ??? ?throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction"); ?? ?} ?? ?DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; ?? ?if (defStatus.isLocalRollbackOnly()) { ?? ??? ?if (defStatus.isDebug()) { ?? ??? ??? ?logger.debug("Transactional code has requested rollback"); ?? ??? ?} ?? ??? ?processRollback(defStatus, false); ?? ??? ?return; ?? ?} ?? ?if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { ?? ??? ?if (defStatus.isDebug()) { ?? ??? ??? ?logger.debug("Global transaction is marked as rollback-only but transactional code requested commit"); ?? ??? ?} ?? ??? ?processRollback(defStatus, true); ?? ??? ?return; ?? ?} ?? ?//執(zhí)行事務(wù)提交 ?? ?processCommit(defStatus); }
好了,看到這大家都明白了吧,在commit中,Spring會(huì)去判斷defStatus.isGlobalRollbackOnly有沒有拋出過異常被Spring所攔截,如果有,那么就不會(huì)執(zhí)行commit操作,轉(zhuǎn)而執(zhí)行processRollback回滾操作
總結(jié)
在Spring的REQUIRED中,只要異常被Spring捕獲到過,那么Spring最終就會(huì)回滾整個(gè)事務(wù),即使自己在業(yè)務(wù)中已經(jīng)捕獲
所以我們回到最初的代碼,如果我們希望Spring不進(jìn)行回滾,那么我們只用將try-cache方法insertC方法中就可以,因?yàn)榇藭r(shí)拋出的異常并不會(huì)被Spring所攔截到
到此這篇關(guān)于Spring事務(wù)捕獲異常后依舊回滾的解決的文章就介紹到這了,更多相關(guān)Spring事務(wù)捕獲異常后回滾 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信公眾號(hào)獲取access_token的方法實(shí)例分析
這篇文章主要介紹了微信公眾號(hào)獲取access_token的方法,結(jié)合實(shí)例形式分析了java實(shí)現(xiàn)微信公眾號(hào)獲取access_token的相關(guān)原理、實(shí)現(xiàn)方法及操作注意事項(xiàng),需要的朋友可以參考下2019-10-10Java中LocalCache本地緩存實(shí)現(xiàn)代碼
本篇文章主要介紹了Java中LocalCache本地緩存實(shí)現(xiàn)代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05spring boot使用自定義配置的線程池執(zhí)行Async異步任務(wù)
這篇文章主要介紹了spring boot使用自定義配置的線程池執(zhí)行Async異步任務(wù),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-01-01