1. 程式人生 > >Spring事務傳播機制小記

Spring事務傳播機制小記

前言

之前對spring的事務傳播機制沒有概念,花點時間去看了事務的原始碼,以及這些事務傳播機制使用的文件,在此做一下簡單的筆記

正文

下面說提到的共享事務的意思就是幾個service共用同一個事務,如傳播機制Propagation.REQUIRED

從原始碼看AOP如何實現事務

我們想使用事務,那就得配置spring元資料,配置事務管理器以及aop的事務的切面,當然可以在spring的xml配置檔案中配置,也可以使用註解,其結果是一樣的。
在aop的切面中,配置了切點,IOC在讀取元資料資訊,進而裝配,最後將元資料例項化快取到IOC容器的過程中,根據配置的切面,符合切點的所有的類,將會代理建立例項物件,將它快取到IOC容器中。打個比方,事務的織入一般應用在service層,那麼我們獲取IOC中的某個service物件,其實已經是動態代理建立的物件。
具體請看文章

《spring的IOC與AOP讀原始碼小記》第二部分

這裡面userService與logonService就是動態代理建立的物件

@Transactional(propagation=Propagation.REQUIRES_NEW)
    public void register(RegisterDTO dto) {
        userService.addUser(dto);
        logonService.addLogon(dto);
}

上面的程式碼,如logonService.addLogon執行的時候到底做了些什麼?

在logonService.addLogon這個程式碼上加入了一個斷點,我們F5進去,發現進入了invoke方法中,這說明了是使用了jdk動態代理建立的物件。

在invoke中執行了下面關鍵的操作
獲取事務的攔截器鏈,這裡也就是獲取了為TransactionInterceptor的增強

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

轉移到ReflectiveMethodInvocation這個類中處理。也就是需要依賴這個類的功能協助完成事務織入

// We need to create a method invocation...
                invocation = new
ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed();

在proceed中,很有意思,我覺得這裡就像是一個遞迴,先執行TransactionInterceptor事務的織入,在執行原目標的方法得到結果
遞迴的結構如: A -> B -> A 攔截器鏈執行完了,就執行原目標方法跳出遞迴

先執行TransactionIntercetor事務織入,看看TransactionInterceptor是如何處理吧

public Object invoke(final MethodInvocation invocation) throws Throwable {
        // Work out the target class: may be {@code null}.
        // The TransactionAttributeSource should be passed the target class
        // as well as the method, which may be from an interface.
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

        // Adapt to TransactionAspectSupport's invokeWithinTransaction...
        return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
            @Override
            public Object proceedWithInvocation() throws Throwable {
                return invocation.proceed();
            }
        });
}

這裡invocation.proceed() 回撥函式就是跳回到ReflectiveMethodInvocation中執行,由於事務攔截器只有一個,所以執行的是所代理的類的被呼叫的方法,返回結果

invokeWithinTransaction是織入事務,有三個比較重要的處理方法

開啟事務

TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

執行被代理的service的呼叫的方法。下面方法就是執行上面所說的invocation.proceed,回去執行service呼叫的方法

retVal = invocation.proceedWithInvocation();

如果出現異常,那麼在這個方法中回滾,這個方法預設如果是檢查型異常則提交,如果RuntimeException 和 error則回滾

completeTransactionAfterThrowing(txInfo, ex);

這裡執行了程式碼的提交

commitTransactionAfterReturning(txInfo);

下面主要是從這三個方法中去講述

從原始碼中看傳播機制如何建立事務

開啟事務

TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

在裡面有一個getTransaction方法

1、某一個service執行的時候,發現當前已經存在一個事務了,那麼它執行下面的程式碼邏輯,根據傳播機制選擇在handleExistingTransaction中處理,是否要重新建立事務,還是使用原來的事務

if (isExistingTransaction(transaction)) {
            // Existing transaction found -> check propagation behavior to find out how to behave.
            return handleExistingTransaction(definition, transaction, debugEnabled);
}

我們看看handleExistingTransaction如何處理的
①、PROPAGATION_NEVER,非事務執行,存在事務就報錯

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
            throw new IllegalTransactionStateException(
                    "Existing transaction found for transaction marked with propagation 'never'");
}

②、PROPAGATION_NOT_SUPPORTED,非事務執行,如果有事務則掛起

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
            if (debugEnabled) {
                logger.debug("Suspending current transaction");
            }
            Object suspendedResources = suspend(transaction);
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(
                    definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}

③、PROPAGATION_REQUIRES_NEW,如果當前有事務則掛起,建立新的事務,沒有事務則建立新事務。

程式碼就不貼了,太長了,反正handleExistingTransaction處理的就是存在事務了,這些傳播機制是如何處理的。

2、如果沒有存在事務,那麼傳播機制為PROPAGATION_MANDATORY,丟擲異常,如果是PROPAGATION_REQUIRED這些就建立新事務

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException(
                    "No existing transaction found for transaction marked with propagation 'mandatory'");
        }
        else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    doBegin(transaction, definition);
}

doBegin是HibernateTransactionManager的方法,它去開啟一個事物

從原始碼看多個事務回滾

completeTransactionAfterThrowing(txInfo, ex)這個方法中去回滾

1、在這個方法裡面有txInfo.transactionAttribute.rollbackOn(ex)判斷,如果是RuntimeException和Error的異常,則回滾

txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());

這個rollback對於不同的傳播機制,處理不同

有savepoint就回滾到儲存的回滾點,這個是NESTED的傳播機制處理

if (status.hasSavepoint()) {
                    if (status.isDebug()) {
                        logger.debug("Rolling back transaction to savepoint");
                    }
                    status.rollbackToHeldSavepoint();
                }

如果這個status是新事務,則滾回。每一個service呼叫的方法,織入事務,都有一個狀態。如果是共享事物的service,除了是事務建立最外層的service,它的isNewTransaction是true,其它所共享事務的service的status狀態的isNewTransaction為false

else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction rollback");
                    }
                    doRollback(status);
                }

打個比方

@Transactional(propagation=Propagation.REQUIRES_NEW)
    public void register(RegisterDTO dto) {
        userService.addUser(dto);
        logonService.addLogon(dto);
}

register方法是建立一個新的事物,它是最外層的事物邊界,userService與logonService的傳播機制為REQUIRED,它們相同register建立的事物,它們的status的isNewTransaction為false,
從上面看,只有到了register才真正的回滾

那麼userService與logonService需要回滾做什麼呢?

存在事務,說明是共享事物了,那麼就標記rollback-only,到最外層的時候一起回滾,如userService和logonService只是表示成rollback-only,丟擲的異常在register的事務織入中捕捉,並真正在register中回滾,它的isNewTransaction為true

else 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");
                        }
                    }
                }

注意的地方:從上面看共享事物,不是最外層的邊界的service,其他的標記成rollback-only,這就要求這些service所丟擲的異常,不能try catch 捕獲,不然的話,在register,發現沒有從這些共享service丟擲異常,直接提交,從而丟擲rollback-only的異常
具體請看文章《Spring事務異常rollback-only》

2、如果是檢查型異常則提交
想檢查型異常,出錯了,被捕獲,但是不會去回滾,直接提交了。事務註解裡面或者配置檔案裡面可以對這些異常做處理從而處理回滾
如果事務

最後說一下,不管是什麼事務傳播機制,這些service嵌套了以後,最外層的service總能捕獲某一個service丟擲的RuntimeException異常,從而回滾。比如register中嵌套了userService和logonService,
不過userService和logonService是什麼傳播機制型別,裡面丟擲的執行時異常,總能在register事務織入catch到的,從而也一起回滾了
。logonService 是requires_new 那麼裡面丟擲的異常就在register中try掉,讓userService的處理能夠順利完成。但是try catch使用也要小心,如果是共享事務的service,try-catch,那麼就會出現rollback-only異常。這些都是看原始碼明白的,實戰是不是這樣處理我還真不知道,沒用過

從原始碼看多個事務的提交

commitTransactionAfterReturning,這裡是提交,在這個方法中通過下面程式碼處理的

txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());

裡面commit程式碼

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);
            return;
        }
        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);
    }

下面這段程式碼如果發現是全域性rollback-only標識在,那麼說明之前共享事物某個service已經出現異常了,判斷是不是該共享事物的最外層service,如果是,rollback回滾,並丟擲rollback-only,沒錯,丟擲異常,為什麼呢,因為rollback-only為true,最外層不應該在commitTransactionAfterReturning處理的,而是completeTransactionAfterThrowing處理

如果不是最外層,那麼繼續標記成rollback-only,在最外層以後再做處理,我看最外層還是拋rollback-only,因為之前共享事物,已經有rollback-only為true了,那麼為什麼會在commitTransactionAfterReturning處理,我看是在register程式碼中就try cacth掉那個異常,從而沒有捕獲,進入completeTransactionAfterThrowing處理異常

if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {

processCommit方法中處理
從程式碼上看,對有savepoint的處理

if (status.hasSavepoint()) {
                    if (status.isDebug()) {
                        logger.debug("Releasing transaction savepoint");
                    }
                    status.releaseHeldSavepoint();
                }

對當前有isNewTransaction為true的處理,直接提交,如果是共享事務最外層邊界,直接提交

else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction commit");
                    }
                    doCommit(status);
                }

如果是共享事務,不是最外層,提交跳過了,不會提交,在最外層邊界的service執行事務的時候才提交

事務傳播機制例項

具體的請看文章,第二篇文章比較好,將多個事務,用jdbc的形式表現出來了,也就是我上面分析,它整體的用jdbc程式碼表現出來,更容易理解
《spring事務傳播機制例項講解》
《詳解spring事務屬性》