<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é)好了!

          共 18288字,需瀏覽 37分鐘

           ·

          2020-12-05 12:22

          點擊上方?好好學(xué)java?,選擇?星標(biāo)?公眾號

          重磅資訊、干貨,第一時間送達

          今日推薦:硬剛一周,3W字總結(jié),一年的經(jīng)驗告訴你如何準(zhǔn)備校招!

          個人原創(chuàng)100W+訪問量博客:點擊前往,查看更多

          作者:蚊子squirrel

          www.jianshu.com/p/a4229aa79ace

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

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

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

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

          1. ?聲明式

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

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

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

          2. ?編程式

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

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

          目前大部分項目使用的是聲明式的后兩種:

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

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

          接下來我們詳細(xì)看下Spring事務(wù)的源代碼,進而了解其工作原理。我們從標(biāo)簽的解析類開始:

          @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ù)的核心實現(xiàn)類TransactionInterceptor及其父類TransactionAspectSupport,其實現(xiàn)了事務(wù)的開啟、數(shù)據(jù)庫操作、事務(wù)提交、回滾等。我們平時在開發(fā)時如果想確定是否在事務(wù)中,也可以在該方法進行斷點調(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ù)的整個調(diào)用流程,但還有一個重要的機制沒分析到,那就是Spring 事務(wù)針對不同的傳播級別控制當(dāng)前獲取的數(shù)據(jù)庫連接。接下來我們看下Spring獲取連接的工具類DataSourceUtils,JdbcTemplate、Mybatis-Spring也都是通過該類獲取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也是一個事務(wù)同步管理的核心類,它實現(xiàn)了事務(wù)同步管理的職能,包括記錄當(dāng)前連接持有connection holder。

          搜索Java知音公眾號,回復(fù)“后端面試”,送你一份Java面試題寶典.pdf

          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ù)時,會處理不同的事務(wù)傳播行為,例如當(dāng)前存在事務(wù),但調(diào)用方法事務(wù)傳播級別為REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED時,對當(dāng)前事務(wù)進行掛起、恢復(fù)等操作,以此保證了當(dāng)前數(shù)據(jù)庫操作獲取正確的Connection。

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

          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ù)是通過AOP代理類中的一個Advice(TransactionInterceptor)進行生效的,而傳播級別定義了事務(wù)與子事務(wù)獲取連接、事務(wù)提交、回滾的具體方式。

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

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

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

          搜索Java知音公眾號,回復(fù)“后端面試”,送你一份Java面試題寶典.pdf

          CGLIB 創(chuàng)建代理的速度比較慢,但創(chuàng)建代理后運行的速度卻非常快,而 JDK 動態(tài)代理正好相反。如果在運行的時候不斷地用 CGLIB 去創(chuàng)建代理,系統(tǒng)的性能會大打折扣。因此如果有接口,Spring默認(rèn)使用JDK 動態(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代理的兩種特點后,我們也就知道在做事務(wù)切面配置時的一些注意事項,例如JDK代理時方法必須是public,CGLIB代理時必須是public、protected,且類不能是final的;在依賴注入時,如果屬性類型定義為實現(xiàn)類,JDK代理時會報如下注入異常:

          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代理時則會成功注入,所以如果有接口,建議注入時該類屬性都定義為接口。另外事務(wù)切點都配置在實現(xiàn)類和接口都可以生效,但建議加在實現(xiàn)類上。

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

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

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

          通過之前章節(jié),相信您已經(jīng)掌握了spring事務(wù)的使用方式與原理,不過還是要注意,因為一不小心就可能就掉坑。首先看第一個坑:

          3.1 事務(wù)不生效

          測試代碼,事務(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.運行輸出:

          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.運行輸出:

          insertAccount connection hashcode=1333810223
          insertStock connection hashcode=1623009085

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

          3.運行輸出:

          insertAccount connection hashcode=303240439
          insertStock connection hashcode=303240439

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

          究其原因,還是因為Spring的事務(wù)本質(zhì)上是個代理類,而本類方法直接調(diào)用時其對象本身并不是織入事務(wù)的代理,所以事務(wù)切面并未生效。具體可以參見#Spring事務(wù)實現(xiàn)機制#章節(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)依賴,親測可以?
          ????public?void?openTx(String?aname,?double?money)?{
          ????????????stockProcessService.openAccount(aname,money);
          ????????????stockProcessService.openStockInAnotherDb?(aname,11);
          ????}

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

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

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

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

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

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

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

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

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

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

          3.2 事務(wù)不回滾

          測試代碼:

          <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("購買股票異常");
          ????????}
          ????????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卻進行了提交。究其原因,直接看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);
          ????}

          }

          由代碼可見,Spring事務(wù)默認(rèn)只對RuntimeException和Error進行回滾,如果應(yīng)用需要對指定的異常類進行回滾,可配置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. 拋出的異常不屬于運行時異常(例如IOException),

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

          接下來我們看下Spring事務(wù)的第三個坑:

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

          測試代碼:


          ????<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ù)庫操作之后超時
          ????????}?catch?(InterruptedException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}
          ????@Test
          ????public?void?testTimeout()?{
          ????????service.openAccountForLongTime("dcbs",?10000);
          ????}

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

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

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

          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)

          通過源碼看看Spring事務(wù)超時的判斷機制:

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

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

          ????
          ????<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;
          ????}

          運行正常,事務(wù)超時失效

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

          搜索Java知音公眾號,回復(fù)“后端面試”,送你一份Java面試題寶典.pdf

          四、 總結(jié)

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

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

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

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

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

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

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

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

          推薦文章

          原創(chuàng)電子書

          歷時整整一年總結(jié)的?Java 面試 + Java 后端技術(shù)學(xué)習(xí)指南,這是本人這幾年及校招的總結(jié),各種高頻面試題已經(jīng)全部進行總結(jié),按照章節(jié)復(fù)習(xí)即可,已經(jīng)拿到了大廠offer。

          原創(chuàng)思維導(dǎo)圖

          掃碼或者微信搜?程序員的技術(shù)圈子?回復(fù)?面試?領(lǐng)取原創(chuàng)電子書和思維導(dǎo)圖。


          瀏覽 17
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  中文字幕有码无码人妻aV蜜桃 | 男女办事网 | 欧美黄色免费第一看 | 使劲操影院 | 自拍视频青娱乐 |