事務方法A調用事務方法B,方法B拋出的異常被方法A catch後會發生什麼?

關注“Java藝術”一起來充電吧!

事務方法A調用事務方法B,當方法B拋出的異常被方法A catch後會發生什麼?

1

場景描述

在一個事務方法中調用另一個事務方法。如在ServiceAmethodA方法中調用ServiceBmethodB方法,兩個方法都設置了事務,傳播機制都是PROPAGATION_REQUIRED

ServiceBmethodB方法聲明事務如下。

public class ServiceB{

    @Transactional(rollbackFor = Exception.class)
    public void methodB(){
        
    }
}

methodA方法中捕獲methodB異常,代碼如下。

public class ServiceA{
    
    public void methodA(){
        try{
            serviceB.methodB();
        }catch(Exception e){
            // do
        }
    }
}

methodA沒有加事務註解,但methodA是在事務中執行的,也是因爲如此,我才調試了半天Spring事務源碼。其效果等同於:

public class ServiceA{
    
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void methodA(){
        try{
            serviceB.methodB();
        }catch(Exception e){
            // do
        }
    }
}

methodB方法拋出異常後,當前事務回滾,異常往外拋出,被methodA方法catch。由於methodA方法catch了異常,異常不再往外拋出,當methodA方法執行完成時,事務切面走的不是回滾邏輯,而是提交邏輯。這就出現瞭如下異常。

異常信息:

Transaction rolled back because it has been marked as rollback-only


2

異常原因追溯

由於methodB方法拋出異常導致事務已經回滾,且當前事務被標誌爲僅回滾,因此當前事務只能回滾,不能再執行提交,如果執行提交,就能看到上述異常。該異常在AbstractPlatformTransactionManagerprocessRollback方法拋出。該方法源碼如下。

public abstract class AbstractPlatformTransactionManage{
    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);
	}
    }
}


3

沒有聲明事務爲什麼會存在事務?

雖然方法沒有聲明事務,可是該方法卻在事務中執行,那麼我們可以在TransactionAspectSupportinvokeWithinTransaction方法中下斷點調試。invokeWithinTransaction方法中會調用TransactionAttributeSourcegetTransactionAttribute方法獲取事務的配置信息。

如使用註解聲明事務時,會調用AnnotationTransactionAttributeSourcegetTransactionAttribute方法。經調試得知,這裏調用的是NameMatchTransactionAttributeSourcegetTransactionAttribute方法,如下圖所示。

ServiceAmethodA方法匹配了'*'這一項。可是這又是在哪裏配置的呢?只要找出在哪裏配置的,將配置去掉問題也就能解決了。

首先找到nameMap字段是在什麼時候初始化的,什麼時候賦值的。

看源碼可知:在NameMatchTransactionAttributeSourcesetProperties方法中調用setNameMap方法爲nameMap字段賦值,而setProperties方法由TransactionAspectSupportsetTransactionAttributes調用,該方法的源碼如下。

public void setTransactionAttributes(Properties transactionAttributes) {
	NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
	tas.setProperties(transactionAttributes);
	this.transactionAttributeSource = tas;
}

再繼續查看哪裏會調用TransactionAspectSupportsetTransactionAttributes方法。

最終找到是項目中配置事務攔截器時注入的。

因爲我對這個項目不熟悉,所以纔有這麼一波源碼分析的操作。

4

這個事務攔截器是怎麼生效的呢?

這個事務攔截器是怎麼生效的?答案是通過InstantiationAwareBeanPostProcessor代理bean,攔截beanpublic方法的執行,交給事務攔截器TransactionInterceptor處理。項目中的配置如下。

    @Bean
    public BeanNameAutoProxyCreator getBeanNameAutoProxyCreator() {
        BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
        // 設置方法攔截器的bean名稱
        creator.setInterceptorNames("getTransactionInterceptor");
        // 攔截哪些bean
        creator.setBeanNames("*Service", "*ServiceImpl");
        // 使用cglib
        creator.setProxyTargetClass(true);
        creator.setOrder(100);
        return creator;
    }


5

解決方案

在與同事溝通後,本來想將這些配置去掉,但去掉後會導致一些事務方法不生效,如:

public class Servie{
    
    public void method1(){
      this.method2();
    }
    
    @Transactional
    public void method2(){
        
    }
    
}

如上面代碼所示,這種情況下method2方法的事務是不生效的。method1方法雖然沒有加事務註解,但由於加了BeanNameAutoProxyCreator配置,等同於給該方法加了事務註解,所以methid1方法的事務生效,所以method2也能在事務中執行。

去掉配置後對系統的影響很大,事務不生效會引發很多問題,將整個系統讓測試部門重新測試一遍也不現實。那麼怎麼解決這個問題呢?

既然所有業務類的public方法都會被放在事務中執行,那麼我就添加一個註解@NotNeedTransactional,被該註解聲明的方法不在事務中執行,與@Transactional的作用正好相反。這樣問題就能解決。(爲什麼不把catch去掉呢?業務需要,不拋出異常)

那麼,怎麼讓@NotNeedTransactional註解生效呢?

繼承事務攔截器,重寫invoke方法,判斷如果方法加了@NotNeedTransactional註解,則直接調用方法,不走切面。代碼如下。

@Bean(name = "getTransactionInterceptor")
public TransactionInterceptor getTransactionInterceptor(AbstractPlatformTransactionManager transactionManager) {
    TransactionInterceptor ti = new TransactionInterceptor() {
       
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            //  有@NotNeedTransaction註解
            if (invocation.getMethod().getAnnotation(NotNeedTransaction.class) != null) {
                return invocation.proceed();
            } else {
                return super.invoke(invocation);
            }
        }
    };
    ti.setTransactionManager(transactionManager);
    ti.setTransactionAttributes(getTransactionAttributes());
    return ti;
}


6

END

本篇給出的解決方案我個人也不建議使用,也是因爲目前想不到完美的解決方案。

如果這個配置不去掉,未來可能遇到的問題會更多。比如可能會將原本一個小的事務變成一個大事務。

公衆號:Java藝術

掃碼關注最新動態

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