“
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,非常感謝 ^_^