Spring事務UnexpectedRollbackException異常拋出原因深度分析及解決方案

Transaction rolled back because it has been marked as rollback-only,
中文翻譯爲:事務已回滾,因爲它被標記成了只回滾。

 

這個異常,相信寫代碼多年的大家,都遇到過,什麼原因呢?今天我們專門分析一下,以爲前車之鑑。

 

報錯信息詳情

 

關鍵報錯信息:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

 

詳細報錯信息如下:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:485)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
	at sun.reflect.GeneratedMethodAccessor3052.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:620)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:609)
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:68)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
	at com.dangdang.ddframe.job.executor.type.SimpleJobExecutor.process(SimpleJobExecutor.java:41)
	at com.dangdang.ddframe.job.executor.AbstractElasticJobExecutor.process(AbstractElasticJobExecutor.java:207)
	at com.dangdang.ddframe.job.executor.AbstractElasticJobExecutor.access$000(AbstractElasticJobExecutor.java:47)
	at com.dangdang.ddframe.job.executor.AbstractElasticJobExecutor$1.run(AbstractElasticJobExecutor.java:186)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.runInterruptibly(TrustedListenableFutureTask.java:108)
	at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:41)
	at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:77)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

 

通常我們的代碼是這樣的:

@Transactional
private void springTransactionTest(){
    update();
}

 

解決方案

 

方案一:捕獲拋出異常的代碼塊,並且不手動throws RuntimeException

 

@Transactional
private void springTransactionTest(){

    try {
        update();
    } catch (Exception e) {
        logger.info("update error:", e);
    }
}

 

方案二:手動設置當前事務爲rollbackOnly

 

@Transactional
private void springTransactionTest(){
	TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    update();
}

如果是包了2層try catch,則是下面這樣的

 

try {
    update();
} catch (Exception e) {
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    logger.info("update error:", e);
}

或者這樣的

try {
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    update();
} catch (Exception e) {
    logger.info("update error:", e);
}

 

方案三:@Transactional(readOnly = true, rollbackFor = Exception.class)

 

這種方案僅僅適用於使用了@Transactional註解的情況。

@Transactional(readOnly = true, rollbackFor = Exception.class)
private void springTransactionTest(){
    update();
}

 

源碼分析

 

關於異常回滾,Spring官方文檔是這樣描述的:
spring聲明式事務管理默認對非檢查異常和運行時異常進行事務回滾,而對檢查異常則不進行回滾操作。

非檢查異常,就是沒有try catch的異常。
檢查異常,就是try catch的異常(catch中不能再拋出異常)。

來,看源碼,有理有據!

AbstractPlatformTransactionManager.commit() 源碼

 

/**
 * This implementation of commit handles participating in existing
 * transactions and programmatic rollback requests.
 * Delegates to {@code isRollbackOnly}, {@code doCommit}
 * and {@code rollback}.
 * @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
 * @see #doCommit
 * @see #rollback
 */
@Override
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;

	// 方案二,手動設置rollbackOnly,其實就是滿足了這個場景。 defStatus.isLocalRollbackOnly() = true
	// 滿足條件後,直接從這裏執行回滾的邏輯,並返回了。
	if (defStatus.isLocalRollbackOnly()) {
		if (defStatus.isDebug()) {
			logger.debug("Transactional code has requested rollback");
		}
		processRollback(defStatus);
		return;
	}

	// 方案一,捕獲異常,其實就是滿足了這個場景
	// 我們一旦捕獲了異常,並且沒有向外拋出異常,那麼其實jvm就感知不到發生了異常。
	if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
		if (defStatus.isDebug()) {
			logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
		}
		processRollback(defStatus);
		// Throw UnexpectedRollbackException only at outermost transaction boundary
		// or if explicitly asked to.
		if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
			throw new UnexpectedRollbackException(
					"Transaction rolled back because it has been marked as rollback-only");
		}
		return;
	}

	processCommit(defStatus);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章