Spring 事務(wù)的那些坑,都在這里了!

Java技術(shù)棧
www.javastack.cn
關(guān)注閱讀更多優(yōu)質(zhì)文章
作者:蚊子squirrel
來(lái)源:www.jianshu.com/p/a4229aa79ace
Spring框架已是JAVA項(xiàng)目的標(biāo)配,其中Spring事務(wù)管理也是最常用的一個(gè)功能,但如果不了解其實(shí)現(xiàn)原理,使用姿勢(shì)不對(duì),一不小心就可能掉坑里。
為了更透徹的說(shuō)明這些坑,本文分四部分展開闡述:
第一部分簡(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)簽的解析類開始:
@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ù)的開啟、數(shù)據(jù)庫(kù)操作、事務(wù)提交、回滾等。我們平時(shí)在開發(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在事務(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è)。
Spring Boot?理論和實(shí)戰(zhàn)倉(cāng)庫(kù):
https://github.com/javastacks/spring-boot-best-practice
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ù)提交、回滾的具體方式。詳解 Java 中的三種代理模式,這篇推薦看下。
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)橐徊恍⌒木涂赡芫偷艨印?/p>
首先看第一個(gè)坑:
3.1 事務(wù)不生效
測(cè)試代碼,事務(wù)AOP配置:
"txAdvice"?transaction-manager="myTxManager">
????
????"openAccount"?isolation="DEFAULT"?propagation="REQUIRED"/>
????"openStock"?isolation="DEFAULT"?propagation="REQUIRED"/>
????"openStockInAnotherDb"?isolation="DEFAULT"?propagation="REQUIRES_NEW"/>
????"openTx"?isolation="DEFAULT"?propagation="REQUIRED"/>
????"openWithoutTx"?isolation="DEFAULT"?propagation="NEVER"/>
????"openWithMultiTx"?isolation="DEFAULT"?propagation="REQUIRED"/>
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ù)切面并未生效。具體可以關(guān)注公眾號(hào)Java技術(shù)棧,在后臺(tái)回復(fù) Spring?獲取系列教程。
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)景:
事務(wù)切面未配置正確 本類方法調(diào)用 多線程調(diào)用 繞開Spring獲取數(shù)據(jù)庫(kù)連接
接下來(lái)我們看下Spring的事務(wù)的另外一個(gè)坑:
3.2 事務(wù)不回滾
測(cè)試代碼:
"txAdvice"?transaction-manager="myTxManager">
????
????????
????????"buyStock"?isolation="DEFAULT"?propagation="REQUIRED"/>
????
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);
????}
…
}
由代碼可見,Spring事務(wù)默認(rèn)只對(duì)RuntimeException和Error進(jìn)行回滾,如果應(yīng)用需要對(duì)指定的異常類進(jìn)行回滾,可配置rollback-for=屬性,例如:
"txAdvice"?transaction-manager="myTxManager">
????
????????
????????"buyStock"?isolation="DEFAULT"?propagation="REQUIRED"?rollback-for="StockException"/>
????
事務(wù)不回滾的原因:
事務(wù)配置切面未生效 應(yīng)用方法中將異常捕獲 拋出的異常不屬于運(yùn)行時(shí)異常(例如IOException), rollback-for屬性配置不正確
接下來(lái)我們看下Spring事務(wù)的第三個(gè)坑:
3.3 事務(wù)超時(shí)不生效
測(cè)試代碼:
????"txAdvice"?transaction-manager="myTxManager">
????????
?????????????"openAccountForLongTime"?isolation="DEFAULT"?propagation="REQUIRED"?timeout="3"/>
????????
????
@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è):
"txAdvice"?transaction-manager="myTxManager">
????
????????"openAccountForLongTimeWithoutJdbcTemplate"?isolation="DEFAULT"?propagation="REQUIRED"?timeout="3"/>
????
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é)
JDBC規(guī)范中Connection 的setAutoCommit是原生控制手動(dòng)事務(wù)的方法,但傳播行為、異常回滾、連接管理等很多技術(shù)問(wèn)題都需要開發(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






關(guān)注Java技術(shù)??锤喔韶?/strong>


