架構師之路(十三)之探討一下Spring框架中@Transactional註解失效引發的血案及背後機理深究

Spring 事務管理分爲編程式和聲明式兩種。編程式事務指的是通過編碼方式實現事務;聲明式事務基於 AOP,將具體的邏輯與事務處理解耦。

聲明式事務有兩種方式,一種是在配置文件(XML)中做相關的事務規則聲明,另一種是基於 @Transactional 註解的方式

好久沒寫文章了,其實很想寫,一直沒機會,也沒時間,要麼有時間了,就要在家帶孩子,要麼就在出差的路上,一旦出差就更沒自己的時間了,因爲大家都知道的原因唄,搞IT的,我覺得很少能有自己的時間吧,讀者應該都懂得.....

最近一直忙於項目,而且項目確實也很重要,全國人民都在關注,本人在項目中也擔負着很關鍵的責任和使命,至於是啥項目,嘿嘿,恕不概述。反正每天都見不到太陽,回來又很晚,很疲憊,其實有很多內容想表達,奈何精力有限,今天剛好擠出時間,來跟大家聊聊做項目過程中,遇到的坑,這個事務機制就是其中很關鍵的一個。

廢話不多說了,直接跟大家上乾貨

註解事務原理

    我相信但凡有過3、5年開發經驗的讀者,一定知道@Transactional這個註解,(如果不清楚,建議您也別讀我的這篇文章了,因爲即使是讀了,您也看不懂,您說對吧?),註解很簡單,英文單詞也很好翻譯,在Service層目標方法上,添加這麼個玩意,就能實現事務的回滾了,但你真的知道它的使用場景以及使用原理麼?真的沒犯過錯麼?

      別的不多說了,我先用兩張時序圖給大家簡單說一下@Transactional深入實現原理吧,直接上圖:

圖畫的可能不是特別好,大家就先將就着看了哈

解釋一下名詞,以下這幾個即將登場的詞,將會是SpringAop聯盟中最重量級的角色,也是事務管理整個邏輯中的中流砥柱:

1)  CglibAopProxy:

        這玩意是Spring中事務攔截的排頭兵,衝鋒陷陣,使用Cglib代理實現SpringAop家族的切面攔截,專注於代理類、代理對象的生成,是創建

代理的核心方法

2)  AdvisedSupport 

        這貨主要是專注於配置當前代理對象相關信息,掃描Spring容器中的所有Advisiors,並判斷是否爲PointcutAdvisor類型實例,並將符合條件的Advisiors,通過DefaultAdvisorChainFactory適配成MethodInterceptor,最終生成攔截器代理列表,本身不做代理對象創建的動作,只是掃描配置代理信息,確切的講就是生成一個裝有方法攔截器的list集合

3) ReflectiveMethodInvocation

        這貨是唯一實現了MethodInvocation的類,專門用於攔截器鏈中的所有代理類的調用

4) TransactionInterceptor

        這貨是整個事務控制的核心模塊,所有的事務操作都是通過這貨來實現,比如事務的準備、事務開啓、事務綁定、以及事務的提交、異常回滾,是SpringAop聯盟中,切面攔截的最終實現,也就是通過這貨實現了代理方法的前後增強,完成了事務處理

5) TransactionAspectSupport

        這貨是上一個貨(TransactionInterceptor)的父類,是對上一個貨的深層次封裝,核心部件都在這裏實現,不再贅述

上圖中的TransactionInterceptor更深層次的處理邏輯沒有畫全,再給大家上一張圖,用於描述Spring容器在真正託管事務管理之後,後續是如何實現的事務攔截以及事務提交、回滾動作,廢話不多說,直接上圖咯:

    

源碼深入解讀

    CglibAopProxy 核心邏輯

@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    TargetSource targetSource = this.advised.getTargetSource();

    Object var16;
    try {
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        target = targetSource.getTarget();
        Class<?> targetClass = target != null ? target.getClass() : null;
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        } else {
            retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
        }

        retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
        var16 = retVal;
    } finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }

        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }

    }

    return var16;
}

可以看到,在上述的源碼中有個很關鍵的代碼CglibAopProxy.CglibMethodInvocation,這段代碼的含義是創建一個代理類,代理類的類型爲CglibAopProxy,並初始化代理鏈攔截器列表interceptorsAndDynamicMethodMatchers

ReflectiveMethodInvocation 核心邏輯

@Nullable
public Object proceed() throws Throwable {
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return this.invokeJoinpoint();
    } else {
        Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
            return dm.methodMatcher.matches(this.method, this.targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed();
        } else {
            return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
        }
    }
}

可以看到這段代碼的核心聚焦在proceed上,該代碼段其實只做了一件事,那就是將代理鏈攔截器列表做了深層次遍歷,循環掃描代理鏈列表,遞歸調用,直到所有的方法攔截器(MethodInterceptor)都調用結束。

TransactionInterceptor 核心邏輯

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
    TransactionAttributeSource tas = this.getTransactionAttributeSource();
    TransactionAttribute txAttr = tas != null ? tas.getTransactionAttribute(method, targetClass) : null;
    PlatformTransactionManager tm = this.determineTransactionManager(txAttr);
    String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);
    Object result;
    if (txAttr != null && tm instanceof CallbackPreferringPlatformTransactionManager) {
        TransactionAspectSupport.ThrowableHolder throwableHolder = new TransactionAspectSupport.ThrowableHolder();

        try {
            result = ((CallbackPreferringPlatformTransactionManager)tm).execute(txAttr, (status) -> {
                TransactionAspectSupport.TransactionInfo txInfo = this.prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);

                Object var9;
                try {
                    Object var8 = invocation.proceedWithInvocation();
                    return var8;
                } catch (Throwable var13) {
                    if (txAttr.rollbackOn(var13)) {
                        if (var13 instanceof RuntimeException) {
                            throw (RuntimeException)var13;
                        }

                        throw new TransactionAspectSupport.ThrowableHolderException(var13);
                    }

                    throwableHolder.throwable = var13;
                    var9 = null;
                } finally {
                    this.cleanupTransactionInfo(txInfo);
                }

                return var9;
            });
            if (throwableHolder.throwable != null) {
                throw throwableHolder.throwable;
            } else {
                return result;
            }
        } catch (TransactionAspectSupport.ThrowableHolderException var19) {
            throw var19.getCause();
        } catch (TransactionSystemException var20) {
            if (throwableHolder.throwable != null) {
                this.logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                var20.initApplicationException(throwableHolder.throwable);
            }

            throw var20;
        } catch (Throwable var21) {
            if (throwableHolder.throwable != null) {
                this.logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
            }

            throw var21;
        }
    } else {
        TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        result = null;

        try {
            result = invocation.proceedWithInvocation();
        } catch (Throwable var17) {
            this.completeTransactionAfterThrowing(txInfo, var17);
            throw var17;
        } finally {
            this.cleanupTransactionInfo(txInfo);
        }

        this.commitTransactionAfterReturning(txInfo);
        return result;
    }
}

可以看到,該段代碼主要完成了事務的具體操作,也就是事務在Spring容器中從準備開啓、綁定至本地線程變量到最終解綁的整個生命週期,詳細描述了事務是提交的時機、拋出異常回滾的時機以及最終回收的整體邏輯

TransactionAspectSupport 事務提交核心邏輯

protected void commitTransactionAfterReturning(@Nullable TransactionAspectSupport.TransactionInfo txInfo) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }

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

}

可以看到,最底層的事務提交操作,最終還是調用的DatasourceTransactionManager實現,也就是最終將提交的動作交給數據庫去做

TransactionAspectSupport 事務回滾核心邏輯

protected void completeTransactionAfterThrowing(@Nullable TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex);
        }

        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            } catch (TransactionSystemException var6) {
                this.logger.error("Application exception overridden by rollback exception", ex);
                var6.initApplicationException(ex);
                throw var6;
            } catch (Error | RuntimeException var7) {
                this.logger.error("Application exception overridden by rollback exception", ex);
                throw var7;
            }
        } else {
            try {
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
            } catch (TransactionSystemException var4) {
                this.logger.error("Application exception overridden by commit exception", ex);
                var4.initApplicationException(ex);
                throw var4;
            } catch (Error | RuntimeException var5) {
                this.logger.error("Application exception overridden by commit exception", ex);
                throw var5;
            }
        }
    }

}

可以看到,最底層的事務回滾操作,最終還是調用的DatasourceTransactionManager實現,也就是最終將回滾的動作交給數據庫去做

遇到過的大坑

    作者在實際運用過程中,也曾遇到過增加@Transactional註解不生效的情況,通過上文對源碼的分析,可以總結出來,使用註解不生效的情況無非有以下幾種:

1)@Transactional 註解的方法不是public修飾

public CglibMethodInvocation(Object proxy, @Nullable Object target, Method method, Object[] arguments, @Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {
    super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
    this.methodProxy = methodProxy;
    this.publicMethod = Modifier.isPublic(method.getModifiers());
}

因爲源碼已經告訴你了,在創建代理對象的時候,必須是public方法,上述源碼中Modifier.isPublic(method.getModifiers())已經明確了

2)內部方法調用

    同一個類中,如果方法入口沒有加@Transactional 註解,而被調用的方法加了此註解,這種情況視爲無效,如下貼出的代碼示例,因爲這種情況,Spring容器沒法通過Cglib生成代理類,自然也就無法使用TransactionInterceptor攔截了

@Override
public int doSth() {
        doBefore();
        log.info("執行某操作......");
        doAfter();
        return 0;

}
  @Transactional(propagation= Propagation.NOT_SUPPORTED,
   isolation = Isolation.READ_COMMITTED,
   rollbackFor = RuntimeException.class)
   int doBefore() {
         log.info("執行某操作之前......");
        return 0;
}

3)雖然加了try catch,但異常沒有拋出

    這種情況也非常常見,捕獲了異常而沒有向上拋出,而是內部自己消化處理了,那麼雖然使用了動態代理,但由於異常本地被捕獲處理,代理方法是感知不到的,也就沒法觸發事務的回滾機制

4)數據庫本身不支持事務操作

    這種情況一般不常見,因爲事務能否生效關鍵的還是要看底層數據庫是否支持,通過分析源碼可以看出來,最終事務操作還是交由數據庫實現,比如Mysql數據庫,引擎一旦從innodb切換成myIsam,那就從根本上生效了,那就不要談事務是否生效的問題了

寫在文章最後

    OK,這篇文章也是百忙之中抽出有限的時間,一點一點堆出來的,在日常的工作中,會遇到各種各樣的問題,有些問題可能一眼看出原因,但知其然並一定知其所以然,比如這個@Transactional註解,背後的機理還是一知半解,今天給大家囉嗦了那麼多,希望能對大家有所幫助,感謝!

寫作不易,您的鼓勵是我前進的最大動力,如果能切實幫助到您,無論金額多少,都是一份心意 o ( ̄︶ ̄) o,非常感謝 ^_^

 

 

 

 

 

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