<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring事務(wù)的那些坑,這里都給你總結(jié)好了!

          共 18119字,需瀏覽 37分鐘

           ·

          2020-11-28 02:08


          作者:蚊子squirrel

          www.jianshu.com/p/a4229aa79ace

          Spring框架已是JAVA項(xiàng)目的標(biāo)配,其中Spring事務(wù)管理也是最常用的一個(gè)功能,但如果不了解其實(shí)現(xiàn)原理,使用姿勢(shì)不對(duì),一不小心就可能掉坑里。

          為了更透徹的說(shuō)明這些坑,本文分四部分展開(kāi)闡述:第一部分簡(jiǎn)單介紹下Spring事務(wù)集成的幾種方式;第二部分結(jié)合Spring源代碼說(shuō)明Spring事務(wù)的實(shí)現(xiàn)原理;第三部分通過(guò)實(shí)際測(cè)試代碼介紹關(guān)于Spring事務(wù)的坑;第四部分是對(duì)本文的總結(jié)。

          一、Spring事務(wù)管理的幾種方式:

          Spring事務(wù)在具體使用方式上可分為兩大類:

          1. ?聲明式

          • 基于 TransactionProxyFactoryBean的聲明式事務(wù)管理

          • 基于 命名空間的事務(wù)管理

          • 基于 @Transactional 的聲明式事務(wù)管理

          2. ?編程式

          • 基于事務(wù)管理器API 的編程式事務(wù)管理

          • 基于TransactionTemplate 的編程式事務(wù)管理

          目前大部分項(xiàng)目使用的是聲明式的后兩種:

          • 基于 命名空間的聲明式事務(wù)管理可以充分利用切點(diǎn)表達(dá)式的強(qiáng)大支持,使得管理事務(wù)更加靈活。
          • 基于 @Transactional 的方式需要實(shí)施事務(wù)管理的方法或者類上使用 @Transactional 指定事務(wù)規(guī)則即可實(shí)現(xiàn)事務(wù)管理,在Spring Boot中通常也建議使用這種注解方式來(lái)標(biāo)記事務(wù)。

          二、Spring事務(wù)實(shí)現(xiàn)機(jī)制

          接下來(lái)我們?cè)敿?xì)看下Spring事務(wù)的源代碼,進(jìn)而了解其工作原理。我們從標(biāo)簽的解析類開(kāi)始:

          @Override
          public?void?init()?{
          ????????registerBeanDefinitionParser("advice",?new?TxAdviceBeanDefinitionParser());
          ????????registerBeanDefinitionParser("annotation-driven",?new?AnnotationDrivenBeanDefinitionParser());
          ????????registerBeanDefinitionParser("jta-transaction-manager",?new?JtaTransactionManagerBeanDefinitionParser());
          ????}
          }
          class?TxAdviceBeanDefinitionParser?extends?AbstractSingleBeanDefinitionParser?{
          ????@Override
          ????protected?Class?getBeanClass(Element?element)?{
          ????????return?TransactionInterceptor.class;
          ????}
          }

          由此可看到Spring事務(wù)的核心實(shí)現(xiàn)類TransactionInterceptor及其父類TransactionAspectSupport,其實(shí)現(xiàn)了事務(wù)的開(kāi)啟、數(shù)據(jù)庫(kù)操作、事務(wù)提交、回滾等。我們平時(shí)在開(kāi)發(fā)時(shí)如果想確定是否在事務(wù)中,也可以在該方法進(jìn)行斷點(diǎn)調(diào)試。

          TransactionInterceptor:

          public?Object?invoke(final?MethodInvocation?invocation)?throws?Throwable?{
          ????????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();
          ????????????}
          ????????});
          ????}

          TransactionAspectSupport

          protected?Object?invokeWithinTransaction(Method?method,?Class?targetClass,?final?InvocationCallback?invocation)
          ????????????throws?Throwable?
          {

          ????????//?If?the?transaction?attribute?is?null,?the?method?is?non-transactional.
          ????????final?TransactionAttribute?txAttr?=?getTransactionAttributeSource().getTransactionAttribute(method,?targetClass);
          ????????final?PlatformTransactionManager?tm?=?determineTransactionManager(txAttr);
          ????????final?String?joinpointIdentification?=?methodIdentification(method,?targetClass,?txAttr);

          ????????if?(txAttr?==?null?||?!(tm?instanceof?CallbackPreferringPlatformTransactionManager))?{
          ????????????//?Standard?transaction?demarcation?with?getTransaction?and?commit/rollback?calls.
          ????????????TransactionInfo?txInfo?=?createTransactionIfNecessary(tm,?txAttr,?joinpointIdentification);
          ????????????Object?retVal?=?null;
          ????????????try?{
          ????????????????//?This?is?an?around?advice:?Invoke?the?next?interceptor?in?the?chain.
          ????????????????//?This?will?normally?result?in?a?target?object?being?invoked.
          ????????????????retVal?=?invocation.proceedWithInvocation();
          ????????????}
          ????????????catch?(Throwable?ex)?{
          ????????????????//?target?invocation?exception
          ????????????????completeTransactionAfterThrowing(txInfo,?ex);
          ????????????????throw?ex;
          ????????????}
          ????????????finally?{
          ????????????????cleanupTransactionInfo(txInfo);
          ????????????}
          ????????????commitTransactionAfterReturning(txInfo);
          ????????????return?retVal;
          ????????}
          }

          至此我們了解事務(wù)的整個(gè)調(diào)用流程,但還有一個(gè)重要的機(jī)制沒(méi)分析到,那就是Spring 事務(wù)針對(duì)不同的傳播級(jí)別控制當(dāng)前獲取的數(shù)據(jù)庫(kù)連接。接下來(lái)我們看下Spring獲取連接的工具類DataSourceUtils,JdbcTemplate、Mybatis-Spring也都是通過(guò)該類獲取Connection。

          public?abstract?class?DataSourceUtils?{

          public?static?Connection?getConnection(DataSource?dataSource)?throws?CannotGetJdbcConnectionException?{
          ????????try?{
          ????????????return?doGetConnection(dataSource);
          ????????}
          ????????catch?(SQLException?ex)?{
          ????????????throw?new?CannotGetJdbcConnectionException("Could?not?get?JDBC?Connection",?ex);
          ????????}
          ????}

          public?static?Connection?doGetConnection(DataSource?dataSource)?throws?SQLException?{
          ????????Assert.notNull(dataSource,?"No?DataSource?specified");

          ????????ConnectionHolder?conHolder?=?(ConnectionHolder)?TransactionSynchronizationManager.getResource(dataSource);
          ????????if?(conHolder?!=?null?&&?(conHolder.hasConnection()?||?conHolder.isSynchronizedWithTransaction()))?{
          ????????????conHolder.requested();
          ????????????if?(!conHolder.hasConnection())?{
          ????????????????logger.debug("Fetching?resumed?JDBC?Connection?from?DataSource");
          ????????????????conHolder.setConnection(dataSource.getConnection());
          ????????????}
          ????????????return?conHolder.getConnection();
          ????????}

          }

          TransactionSynchronizationManager也是一個(gè)事務(wù)同步管理的核心類,它實(shí)現(xiàn)了事務(wù)同步管理的職能,包括記錄當(dāng)前連接持有connection holder。

          TransactionSynchronizationManager

          private?static?final?ThreadLocal>?resources?=
          ????????????new?NamedThreadLocal>("Transactional?resources");

          public?static?Object?getResource(Object?key)?{
          ????????Object?actualKey?=?TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
          ????????Object?value?=?doGetResource(actualKey);
          ????????if?(value?!=?null?&&?logger.isTraceEnabled())?{
          ????????????logger.trace("Retrieved?value?["?+?value?+?"]?for?key?["?+?actualKey?+?"]?bound?to?thread?["?+
          ????????????????????Thread.currentThread().getName()?+?"]");
          ????????}
          ????????return?value;
          ????}

          ????/**
          ?????*?Actually?check?the?value?of?the?resource?that?is?bound?for?the?given?key.
          ?????*/

          ????private?static?Object?doGetResource(Object?actualKey)?{
          ????????Map?map?=?resources.get();
          ????????if?(map?==?null)?{
          ????????????return?null;
          ????????}
          ????????Object?value?=?map.get(actualKey);
          ????????//?Transparently?remove?ResourceHolder?that?was?marked?as?void...
          ????????if?(value?instanceof?ResourceHolder?&&?((ResourceHolder)?value).isVoid())?{
          ????????????map.remove(actualKey);
          ????????????//?Remove?entire?ThreadLocal?if?empty...
          ????????????if?(map.isEmpty())?{
          ????????????????resources.remove();
          ????????????}
          ????????????value?=?null;
          ????????}
          ????????return?value;
          ????}

          在事務(wù)管理器類AbstractPlatformTransactionManager中,getTransaction獲取事務(wù)時(shí),會(huì)處理不同的事務(wù)傳播行為,例如當(dāng)前存在事務(wù),但調(diào)用方法事務(wù)傳播級(jí)別為REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED時(shí),對(duì)當(dāng)前事務(wù)進(jìn)行掛起、恢復(fù)等操作,以此保證了當(dāng)前數(shù)據(jù)庫(kù)操作獲取正確的Connection。

          具體是在子事務(wù)提交的最后會(huì)將掛起的事務(wù)恢復(fù),恢復(fù)時(shí)重新調(diào)用TransactionSynchronizationManager. bindResource設(shè)置之前的connection holder,這樣再獲取的連接就是被恢復(fù)的數(shù)據(jù)庫(kù)連接, TransactionSynchronizationManager當(dāng)前激活的連接只能是一個(gè)。

          AbstractPlatformTransactionManager

             private?TransactionStatus?handleExistingTransaction(
          ????????????TransactionDefinition?definition,?Object?transaction,?boolean?debugEnabled)

          ????????????throws?TransactionException?
          {

          ????????if?(definition.getPropagationBehavior()?==?TransactionDefinition.PROPAGATION_REQUIRES_NEW)?{
          ????????????if?(debugEnabled)?{
          ????????????????logger.debug("Suspending?current?transaction,?creating?new?transaction?with?name?["?+
          ????????????????????????definition.getName()?+?"]");
          ????????????}
          ????????????SuspendedResourcesHolder?suspendedResources?=?suspend(transaction);
          ????????????try?{
          ????????????????boolean?newSynchronization?=?(getTransactionSynchronization()?!=?SYNCHRONIZATION_NEVER);
          ????????????????DefaultTransactionStatus?status?=?newTransactionStatus(
          ????????????????????????definition,?transaction,?true,?newSynchronization,?debugEnabled,?suspendedResources);
          ????????????????doBegin(transaction,?definition);
          ????????????????prepareSynchronization(status,?definition);
          ????????????????return?status;
          ????????????}
          ????????????catch?(RuntimeException?beginEx)?{
          ????????????????resumeAfterBeginException(transaction,?suspendedResources,?beginEx);
          ????????????????throw?beginEx;
          ????????????}
          ????????????catch?(Error?beginErr)?{
          ????????????????resumeAfterBeginException(transaction,?suspendedResources,?beginErr);
          ????????????????throw?beginErr;
          ????????????}
          ????????}
          /**
          ?????*?Clean?up?after?completion,?clearing?synchronization?if?necessary,
          ?????*?and?invoking?doCleanupAfterCompletion.
          ?????*?@param?status?object?representing?the?transaction
          ?????*?@see?#doCleanupAfterCompletion
          ?????*/

          ????private?void?cleanupAfterCompletion(DefaultTransactionStatus?status)?{
          ????????status.setCompleted();
          ????????if?(status.isNewSynchronization())?{
          ????????????TransactionSynchronizationManager.clear();
          ????????}
          ????????if?(status.isNewTransaction())?{
          ????????????doCleanupAfterCompletion(status.getTransaction());
          ????????}
          ????????if?(status.getSuspendedResources()?!=?null)?{
          ????????????if?(status.isDebug())?{
          ????????????????logger.debug("Resuming?suspended?transaction?after?completion?of?inner?transaction");
          ????????????}
          ????????????resume(status.getTransaction(),?(SuspendedResourcesHolder)?status.getSuspendedResources());
          ????????}
          ????}

          Spring的事務(wù)是通過(guò)AOP代理類中的一個(gè)Advice(TransactionInterceptor)進(jìn)行生效的,而傳播級(jí)別定義了事務(wù)與子事務(wù)獲取連接、事務(wù)提交、回滾的具體方式。

          AOP(Aspect Oriented Programming),即面向切面編程。Spring AOP技術(shù)實(shí)現(xiàn)上其實(shí)就是代理類,具體可分為靜態(tài)代理和動(dòng)態(tài)代理兩大類,其中靜態(tài)代理是指使用 AOP 框架提供的命令進(jìn)行編譯,從而在編譯階段就可生成 AOP 代理類,因此也稱為編譯時(shí)增強(qiáng);(AspectJ);而動(dòng)態(tài)代理則在運(yùn)行時(shí)借助于 默寫類庫(kù)在內(nèi)存中“臨時(shí)”生成 AOP 動(dòng)態(tài)代理類,因此也被稱為運(yùn)行時(shí)增強(qiáng)。其中java是使用的動(dòng)態(tài)代理模式 (JDK+CGLIB)。

          JDK動(dòng)態(tài)代理 JDK動(dòng)態(tài)代理主要涉及到j(luò)ava.lang.reflect包中的兩個(gè)類:Proxy和InvocationHandler。InvocationHandler是一個(gè)接口,通過(guò)實(shí)現(xiàn)該接口定義橫切邏輯,并通過(guò)反射機(jī)制調(diào)用目標(biāo)類的代碼,動(dòng)態(tài)將橫切邏輯和業(yè)務(wù)邏輯編制在一起。Proxy利用InvocationHandler動(dòng)態(tài)創(chuàng)建一個(gè)符合某一接口的實(shí)例,生成目標(biāo)類的代理對(duì)象。

          CGLIB動(dòng)態(tài)代理 CGLIB全稱為Code Generation Library,是一個(gè)強(qiáng)大的高性能,高質(zhì)量的代碼生成類庫(kù),可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口,CGLIB封裝了asm,可以再運(yùn)行期動(dòng)態(tài)生成新的class。和JDK動(dòng)態(tài)代理相比較:JDK創(chuàng)建代理有一個(gè)限制,就是只能為接口創(chuàng)建代理實(shí)例,而對(duì)于沒(méi)有通過(guò)接口定義業(yè)務(wù)方法的類,則可以通過(guò)CGLIB創(chuàng)建動(dòng)態(tài)代理。

          CGLIB 創(chuàng)建代理的速度比較慢,但創(chuàng)建代理后運(yùn)行的速度卻非??欤?JDK 動(dòng)態(tài)代理正好相反。如果在運(yùn)行的時(shí)候不斷地用 CGLIB 去創(chuàng)建代理,系統(tǒng)的性能會(huì)大打折扣。因此如果有接口,Spring默認(rèn)使用JDK 動(dòng)態(tài)代理,源代碼如下:

          public?class?DefaultAopProxyFactory?implements?AopProxyFactory,?Serializable?{

          ????@Override
          ????public?AopProxy?createAopProxy(AdvisedSupport?config)?throws?AopConfigException?{
          ????????if?(config.isOptimize()?||?config.isProxyTargetClass()?||?hasNoUserSuppliedProxyInterfaces(config))?{
          ????????????Class?targetClass?=?config.getTargetClass();
          ????????????if?(targetClass?==?null)?{
          ????????????????throw?new?AopConfigException("TargetSource?cannot?determine?target?class:?"?+
          ????????????????????????"Either?an?interface?or?a?target?is?required?for?proxy?creation.");
          ????????????}
          ????????????if?(targetClass.isInterface()?||?Proxy.isProxyClass(targetClass))?{
          ????????????????return?new?JdkDynamicAopProxy(config);
          ????????????}
          ????????????return?new?ObjenesisCGLIBAopProxy(config);
          ????????}???
          ????????else?{
          ????????????return?new?JdkDynamicAopProxy(config);
          ????????}
          ????}
          }

          在了解Spring代理的兩種特點(diǎn)后,我們也就知道在做事務(wù)切面配置時(shí)的一些注意事項(xiàng),例如JDK代理時(shí)方法必須是public,CGLIB代理時(shí)必須是public、protected,且類不能是final的;在依賴注入時(shí),如果屬性類型定義為實(shí)現(xiàn)類,JDK代理時(shí)會(huì)報(bào)如下注入異常:

          org.springframework.beans.factory.UnsatisfiedDependencyException:?Error?creating?bean?with?name?'com.wwb.test.TxTestAop':?Unsatisfied?dependency?expressed?through?field?'service';?nested?exception?is?org.springframework.beans.factory.BeanNotOfRequiredTypeException:?Bean?named?'stockService'?is?expected?to?be?of?type?'com.wwb.service.StockProcessServiceImpl'?but?was?actually?of?type?'com.sun.proxy.$Proxy14'

          但如果修改為CGLIB代理時(shí)則會(huì)成功注入,所以如果有接口,建議注入時(shí)該類屬性都定義為接口。另外事務(wù)切點(diǎn)都配置在實(shí)現(xiàn)類和接口都可以生效,但建議加在實(shí)現(xiàn)類上。

          官網(wǎng)關(guān)于Spring AOP的詳細(xì)介紹

          https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html%23aop

          三、Spring事務(wù)的那些坑

          通過(guò)之前章節(jié),相信您已經(jīng)掌握了spring事務(wù)的使用方式與原理,不過(guò)還是要注意,因?yàn)橐徊恍⌒木涂赡芫偷艨?。首先看第一個(gè)坑:

          3.1 事務(wù)不生效

          測(cè)試代碼,事務(wù)AOP配置:

          <tx:advice?id="txAdvice"?transaction-manager="myTxManager">
          ????????<tx:attributes>
          ????????????
          ????????????<tx:method?name="openAccount"?isolation="DEFAULT"?propagation="REQUIRED"/>
          ????????????<tx:method?name="openStock"?isolation="DEFAULT"?propagation="REQUIRED"/>
          ????????????<tx:method?name="openStockInAnotherDb"?isolation="DEFAULT"?propagation="REQUIRES_NEW"/>
          ????????????<tx:method?name="openTx"?isolation="DEFAULT"?propagation="REQUIRED"/>
          ????????????<tx:method?name="openWithoutTx"?isolation="DEFAULT"?propagation="NEVER"/>
          ????????????<tx:method?name="openWithMultiTx"?isolation="DEFAULT"?propagation="REQUIRED"/>
          tx:advice>
          public?class?StockProcessServiceImpl?implements?IStockProcessService{
          @Autowired
          ?????private?IAccountDao?accountDao;
          ????@Autowired
          ?????private?IStockDao?stockDao;
          ????
          ????@Override
          ????public?void?openAccount(String?aname,?double?money)?{
          ????????accountDao.insertAccount(aname,?money);
          ????}

          ????@Override
          ????public?void?openStock(String?sname,?int?amount)?{
          ????????stockDao.insertStock(sname,?amount);
          ????}
          ????
          ????@Override
          ????public?void?openStockInAnotherDb(String?sname,?int?amount)?{
          ????????stockDao.insertStock(sname,?amount);
          }
          }
          public?void?insertAccount(String?aname,?double?money)?{
          ????????String?sql?=?"insert?into?account(aname,?balance)?values(?,?)";
          ????????this.getJdbcTemplate().update(sql,?aname,?money);
          ????????DbUtils.printDBConnectionInfo("insertAccount",getDataSource());
          }?

          ????public?void?insertStock(String?sname,?int?amount)?{
          ????????String?sql?=?"insert?into?stock(sname,?count)?values?(?,?)";
          ????????this.getJdbcTemplate().update(sql?,?sname,?amount);
          ????????DbUtils.printDBConnectionInfo("insertStock",getDataSource());
          }

          ????public?static?void?printDBConnectionInfo(String?methodName,DataSource?ds)?{
          ????????Connection?connection?=?DataSourceUtils.getConnection(ds);
          ????????System.out.println(methodName+"?connection?hashcode="+connection.hashCode());
          ????}
          //調(diào)用同類方法,外圍配置事務(wù)
          ????public?void?openTx(String?aname,?double?money)?{
          ????????????openAccount(aname,money);
          ????????????openStock(aname,11);
          ????}

          1.運(yùn)行輸出:

          insertAccount connection hashcode=319558327
          insertStock connection hashcode=319558327

          //調(diào)用同類方法,外圍未配置事務(wù)
          ????public?void?openWithoutTx(String?aname,?double?money)?{
          ????????openAccount(aname,money);
          ????????????openStock(aname,11);
          ????}

          2.運(yùn)行輸出:

          insertAccount connection hashcode=1333810223
          insertStock connection hashcode=1623009085

          //通過(guò)AopContext.currentProxy()方法獲取代理
          @Override
          public?void?openWithMultiTx(String?aname,?double?money)?{
          openAccount(aname,money);??
          openStockInAnotherDb(aname,?11);//傳播級(jí)別為REQUIRES_NEW
          }

          3.運(yùn)行輸出:

          insertAccount connection hashcode=303240439
          insertStock connection hashcode=303240439

          可以看到2、3測(cè)試方法跟我們事務(wù)預(yù)期并一樣,結(jié)論:調(diào)用方法未配置事務(wù)、本類方法直接調(diào)用,事務(wù)都不生效!

          究其原因,還是因?yàn)镾pring的事務(wù)本質(zhì)上是個(gè)代理類,而本類方法直接調(diào)用時(shí)其對(duì)象本身并不是織入事務(wù)的代理,所以事務(wù)切面并未生效。具體可以參見(jiàn)#Spring事務(wù)實(shí)現(xiàn)機(jī)制#章節(jié)。

          Spring也提供了判斷是否為代理的方法:

          public?static?void?printProxyInfo(Object?bean)?{
          ????????System.out.println("isAopProxy"+AopUtils.isAopProxy(bean));
          ????????System.out.println("isCGLIBProxy="+AopUtils.isCGLIBProxy(bean));
          ????????System.out.println("isJdkProxy="+AopUtils.isJdkDynamicProxy(bean));
          ????}

          那如何修改為代理類調(diào)用呢?最直接的想法是注入自身,代碼如下:

          ????@Autowired
          ????private?IStockProcessService?stockProcessService;
          //注入自身類,循環(huán)依賴,親測(cè)可以?
          ????public?void?openTx(String?aname,?double?money)?{
          ????????????stockProcessService.openAccount(aname,money);
          ????????????stockProcessService.openStockInAnotherDb?(aname,11);
          ????}

          當(dāng)然Spring提供了獲取當(dāng)前代理的方法:代碼如下:

          //通過(guò)AopContext.currentProxy()方法獲取代理
          ????@Override
          ????public?void?openWithMultiTx(String?aname,?double?money)?{
          ((IStockProcessService)AopContext.currentProxy()).openAccount(aname,money);

          ((IStockProcessService)AopContext.currentProxy()).openStockInAnotherDb(aname,?11);
          ????}

          另外Spring是通過(guò)TransactionSynchronizationManager類中線程變量來(lái)獲取事務(wù)中數(shù)據(jù)庫(kù)連接,所以如果是多線程調(diào)用或者繞過(guò)Spring獲取數(shù)據(jù)庫(kù)連接,都會(huì)導(dǎo)致Spring事務(wù)配置失效。

          最后Spring事務(wù)配置失效的場(chǎng)景:

          1. 事務(wù)切面未配置正確

          2. 本類方法調(diào)用

          3. 多線程調(diào)用

          4. 繞開(kāi)Spring獲取數(shù)據(jù)庫(kù)連接

          接下來(lái)我們看下Spring的事務(wù)的另外一個(gè)坑:

          3.2 事務(wù)不回滾

          測(cè)試代碼:

          <tx:advice?id="txAdvice"?transaction-manager="myTxManager">
          ????????<tx:attributes>
          ????????????
          ????????????<tx:method?name="buyStock"?isolation="DEFAULT"?propagation="REQUIRED"/>
          ????????tx:attributes>
          tx:advice>
          public?void?buyStock(String?aname,?double?money,?String?sname,?int?amount)?throws?StockException?{
          ????????boolean?isBuy?=?true;
          ????????accountDao.updateAccount(aname,?money,?isBuy);
          ????????//?故意拋出異常
          ????????if?(true)?{
          ????????????throw?new?StockException("購(gòu)買股票異常");
          ????????}
          ????????stockDao.updateStock(sname,?amount,?isBuy);
          ????}
          ????@Test
          ????public?void?testBuyStock()?{
          ????????try?{
          ????????????service.openAccount("dcbs",?10000);
          ????????????service.buyStock("dcbs",?2000,?"dap",?5);
          ????????}?catch?(StockException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????????double?accountBalance?=?service.queryAccountBalance("dcbs");
          ????????System.out.println("account?balance?is?"?+?accountBalance);
          ????}

          輸出結(jié)果:

          insertAccount connection hashcode=656479172
          updateAccount connection hashcode=517355658
          account balance is 8000.0

          應(yīng)用拋出異常,但accountDao.updateAccount卻進(jìn)行了提交。究其原因,直接看Spring源代碼:

          TransactionAspectSupport

          protected?void?completeTransactionAfterThrowing(TransactionInfo?txInfo,?Throwable?ex)?{
          ????????if?(txInfo?!=?null?&&?txInfo.hasTransaction())?{
          ????????????if?(logger.isTraceEnabled())?{
          ????????????????logger.trace("Completing?transaction?for?["?+?txInfo.getJoinpointIdentification()?+
          ????????????????????????"]?after?exception:?"?+?ex);
          ????????????}
          ????????????if?(txInfo.transactionAttribute.rollbackOn(ex))?{
          ????????????????try?{
          ????????????????????txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
          ????????????????}
          ????????????????catch?(TransactionSystemException?ex2)?{
          ????????????????????logger.error("Application?exception?overridden?by?rollback?exception",?ex);
          ????????????????????ex2.initApplicationException(ex);
          ????????????????????throw?ex2;
          ????????????????}
          ????????????????…
          }

          public?class?DefaultTransactionAttribute?extends?DefaultTransactionDefinition?implements?TransactionAttribute?{
          @Override
          ????public?boolean?rollbackOn(Throwable?ex)?{
          ????????return?(ex?instanceof?RuntimeException?||?ex?instanceof?Error);
          ????}

          }

          由代碼可見(jiàn),Spring事務(wù)默認(rèn)只對(duì)RuntimeException和Error進(jìn)行回滾,如果應(yīng)用需要對(duì)指定的異常類進(jìn)行回滾,可配置rollback-for=屬性,例如:

          ????
          ????<tx:advice?id="txAdvice"?transaction-manager="myTxManager">
          ????????<tx:attributes>
          ????????????
          ????????????<tx:method?name="buyStock"?isolation="DEFAULT"?propagation="REQUIRED"?rollback-for="StockException"/>
          ????????tx:attributes>
          ????tx:advice>

          事務(wù)不回滾的原因:

          1. 事務(wù)配置切面未生效

          2. 應(yīng)用方法中將異常捕獲

          3. 拋出的異常不屬于運(yùn)行時(shí)異常(例如IOException),

          4. rollback-for屬性配置不正確

          接下來(lái)我們看下Spring事務(wù)的第三個(gè)坑:

          3.3 事務(wù)超時(shí)不生效

          測(cè)試代碼:


          ????<tx:advice?id="txAdvice"?transaction-manager="myTxManager">
          ????????<tx:attributes>
          ?????????????<tx:method?name="openAccountForLongTime"?isolation="DEFAULT"?propagation="REQUIRED"?timeout="3"/>
          ????????tx:attributes>
          ????tx:advice>
          @Override
          ????public?void?openAccountForLongTime(String?aname,?double?money)?{
          ????????accountDao.insertAccount(aname,?money);
          ????????try?{
          ????????????Thread.sleep(5000L);//在數(shù)據(jù)庫(kù)操作之后超時(shí)
          ????????}?catch?(InterruptedException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}
          ????@Test
          ????public?void?testTimeout()?{
          ????????service.openAccountForLongTime("dcbs",?10000);
          ????}

          正常運(yùn)行,事務(wù)超時(shí)未生效

          public?void?openAccountForLongTime(String?aname,?double?money)?{
          ????????try?{
          ????????????Thread.sleep(5000L);?//在數(shù)據(jù)庫(kù)操作之前超時(shí)
          ????????}?catch?(InterruptedException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????????accountDao.insertAccount(aname,?money);
          ????}

          拋出事務(wù)超時(shí)異常,超時(shí)生效

          org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Nov 23 17:03:02 CST 2018
          at org.springframework.transaction.support.ResourceHolderSupport.checkTransactionTimeout(ResourceHolderSupport.java:141)

          通過(guò)源碼看看Spring事務(wù)超時(shí)的判斷機(jī)制:

          ResourceHolderSupport

          /**
          ?????*?Return?the?time?to?live?for?this?object?in?milliseconds.
          ?????*?@return?number?of?millseconds?until?expiration
          ?????*?@throws?TransactionTimedOutException?if?the?deadline?has?already?been?reached
          ?????*/

          ????public?long?getTimeToLiveInMillis()?throws?TransactionTimedOutException{
          ????????if?(this.deadline?==?null)?{
          ????????????throw?new?IllegalStateException("No?timeout?specified?for?this?resource?holder");
          ????????}
          ????????long?timeToLive?=?this.deadline.getTime()?-?System.currentTimeMillis();
          ????????checkTransactionTimeout(timeToLive?<=?0);
          ????????return?timeToLive;
          ????}

          ????/**
          ?????*?Set?the?transaction?rollback-only?if?the?deadline?has?been?reached,
          ?????*?and?throw?a?TransactionTimedOutException.
          ?????*/

          ????private?void?checkTransactionTimeout(boolean?deadlineReached)?throws?TransactionTimedOutException?{
          ????????if?(deadlineReached)?{
          ????????????setRollbackOnly();
          ????????????throw?new?TransactionTimedOutException("Transaction?timed?out:?deadline?was?"?+?this.deadline);
          ????????}
          ????}

          通過(guò)查看getTimeToLiveInMillis方法的Call Hierarchy,可以看到被DataSourceUtils的applyTimeout所調(diào)用, 繼續(xù)看applyTimeout的Call Hierarchy,可以看到有兩處調(diào)用,一個(gè)是JdbcTemplate,一個(gè)是TransactionAwareInvocationHandler類,后者是只有TransactionAwareDataSourceProxy類調(diào)用,該類為DataSource的事務(wù)代理類,我們一般并不會(huì)用到。難道超時(shí)只能在這調(diào)用JdbcTemplate中生效?寫代碼親測(cè):

          ????
          ????<tx:advice?id="txAdvice"?transaction-manager="myTxManager">
          ????????<tx:attributes>
          ????????????<tx:method?name="openAccountForLongTimeWithoutJdbcTemplate"?isolation="DEFAULT"?propagation="REQUIRED"?timeout="3"/>
          ????????tx:attributes>
          ????tx:advice>
          ????public?void?openAccountForLongTimeWithoutJdbcTemplate(String?aname,?double?money)?{
          ????????try?{
          ????????????Thread.sleep(5000L);
          ????????}?catch?(InterruptedException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????????accountDao.queryAccountBalanceWithoutJdbcTemplate(aname);
          ????}
          ????public?double?queryAccountBalanceWithoutJdbcTemplate(String?aname)?{
          ???????????String?sql?=?"select?balance?from?account?where?aname?=??";
          ???????????PreparedStatement?prepareStatement;
          ????????try?{
          ????????????prepareStatement?=?this.getConnection().prepareStatement(sql);
          ???????????????prepareStatement.setString(1,?aname);
          ???????????????ResultSet?executeQuery?=?prepareStatement.executeQuery();
          ???????????????while(executeQuery.next())?{
          ???????????????????return?executeQuery.getDouble(1);
          ???????????????}
          ????????}?catch?(CannotGetJdbcConnectionException?|?SQLException?e)?{
          ????????????//?TODO?Auto-generated?catch?block
          ????????????e.printStackTrace();
          ????????}
          ????????return?0;
          ????}

          運(yùn)行正常,事務(wù)超時(shí)失效

          由上可見(jiàn):Spring事務(wù)超時(shí)判斷在通過(guò)JdbcTemplate的數(shù)據(jù)庫(kù)操作時(shí),所以如果超時(shí)后未有JdbcTemplate方法調(diào)用,則無(wú)法準(zhǔn)確判斷超時(shí)。另外也可以得知,如果通過(guò)Mybatis等操作數(shù)據(jù)庫(kù),Spring的事務(wù)超時(shí)是無(wú)效的。鑒于此,Spring的事務(wù)超時(shí)謹(jǐn)慎使用。

          四、 總結(jié)

          JDBC規(guī)范中Connection 的setAutoCommit是原生控制手動(dòng)事務(wù)的方法,但傳播行為、異?;貪L、連接管理等很多技術(shù)問(wèn)題都需要開(kāi)發(fā)者自己處理,而Spring事務(wù)通過(guò)AOP方式非常優(yōu)雅的屏蔽了這些技術(shù)復(fù)雜度,使得事務(wù)管理變的異常簡(jiǎn)單。

          但凡事有利弊,如果對(duì)實(shí)現(xiàn)機(jī)制理解不透徹,很容易掉坑里。最后總結(jié)下Spring事務(wù)的可能踩的坑:

          1. ?Spring事務(wù)未生效

          • 調(diào)用方法本身未正確配置事務(wù)
          • 本類方法直接調(diào)用
          • 數(shù)據(jù)庫(kù)操作未通過(guò)Spring的DataSourceUtils獲取Connection
          • 多線程調(diào)用

          2. ?Spring事務(wù)回滾失效

          • 未準(zhǔn)確配置rollback-for屬性
          • 異常類不屬于RuntimeException與Error
          • 應(yīng)用捕獲了異常未拋出

          3. ?Spring事務(wù)超時(shí)不準(zhǔn)確或失效

          • 超時(shí)發(fā)生在最后一次JdbcTemplate操作之后
          • 通過(guò)非JdbcTemplate操作數(shù)據(jù)庫(kù),例如Mybatis

          更多精彩推薦

          ??外包程序員入職螞蟻金服被質(zhì)疑,網(wǎng)友:人生污點(diǎn)
          ??前后端分離三連問(wèn):為何分離?如何分離?分離后的接口規(guī)范?
          ??如何設(shè)計(jì)一個(gè)通用的權(quán)限管理系統(tǒng)
          ??去一家小公司從0到1搭建后端架構(gòu),做個(gè)總結(jié)!
          ??這應(yīng)該是全網(wǎng)最全的Git分支開(kāi)發(fā)規(guī)范手冊(cè)~

          最后,推薦給大家一個(gè)有趣有料的公眾號(hào):寫代碼的渣渣鵬,7年老程序員教你寫bug,回復(fù) 面試或資源 送一你整套開(kāi)發(fā)筆記 有驚喜哦

          瀏覽 41
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  永久免费一区二区 | 国产精品嫩草影院欧美成人精品a | 波多野吉衣高清无码 | 青青国产在线 | 国产精品乱码一区二区三区视频 |