javascript
Spring事务的那些坑,这里都给你总结好了!
點擊上方?好好學java?,選擇?星標?公眾號
重磅資訊、干貨,第一時間送達今日推薦:硬剛一周,3W字總結,一年的經驗告訴你如何準備校招!
個人原創100W+訪問量博客:點擊前往,查看更多作者:蚊子squirrel
www.jianshu.com/p/a4229aa79ace
Spring框架已是JAVA項目的標配,其中Spring事務管理也是最常用的一個功能,但如果不了解其實現原理,使用姿勢不對,一不小心就可能掉坑里。
為了更透徹的說明這些坑,本文分四部分展開闡述:第一部分簡單介紹下Spring事務集成的幾種方式;第二部分結合Spring源代碼說明Spring事務的實現原理;第三部分通過實際測試代碼介紹關于Spring事務的坑;第四部分是對本文的總結。
一、Spring事務管理的幾種方式:
Spring事務在具體使用方式上可分為兩大類:
1. ?聲明式
-
基于 TransactionProxyFactoryBean的聲明式事務管理
-
基于 <tx> 和 <aop> 命名空間的事務管理
-
基于 @Transactional 的聲明式事務管理
2. ?編程式
-
基于事務管理器API 的編程式事務管理
-
基于TransactionTemplate 的編程式事務管理
目前大部分項目使用的是聲明式的后兩種:
-
基于 <tx> 和 <aop> 命名空間的聲明式事務管理可以充分利用切點表達式的強大支持,使得管理事務更加靈活。
-
基于 @Transactional 的方式需要實施事務管理的方法或者類上使用 @Transactional 指定事務規則即可實現事務管理,在Spring Boot中通常也建議使用這種注解方式來標記事務。
二、Spring事務實現機制
接下來我們詳細看下Spring事務的源代碼,進而了解其工作原理。我們從<tx>標簽的解析類開始:
@Override public?void?init()?{registerBeanDefinitionParser("advice",?new?TxAdviceBeanDefinitionParser());registerBeanDefinitionParser("annotation-driven",?new?AnnotationDrivenBeanDefinitionParser());registerBeanDefinitionParser("jta-transaction-manager",?new?JtaTransactionManagerBeanDefinitionParser());} } class?TxAdviceBeanDefinitionParser?extends?AbstractSingleBeanDefinitionParser?{@Overrideprotected?Class<?>?getBeanClass(Element?element)?{return?TransactionInterceptor.class;} }由此可看到Spring事務的核心實現類TransactionInterceptor及其父類TransactionAspectSupport,其實現了事務的開啟、數據庫操作、事務提交、回滾等。我們平時在開發時如果想確定是否在事務中,也可以在該方法進行斷點調試。
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()?{@Overridepublic?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?exceptioncompleteTransactionAfterThrowing(txInfo,?ex);throw?ex;}finally?{cleanupTransactionInfo(txInfo);}commitTransactionAfterReturning(txInfo);return?retVal;} }至此我們了解事務的整個調用流程,但還有一個重要的機制沒分析到,那就是Spring 事務針對不同的傳播級別控制當前獲取的數據庫連接。接下來我們看下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也是一個事務同步管理的核心類,它實現了事務同步管理的職能,包括記錄當前連接持有connection holder。
搜索Java知音公眾號,回復“后端面試”,送你一份Java面試題寶典.pdf
TransactionSynchronizationManager
private?static?final?ThreadLocal<Map<Object,?Object>>?resources?=new?NamedThreadLocal<Map<Object,?Object>>("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<Object,?Object>?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;}在事務管理器類AbstractPlatformTransactionManager中,getTransaction獲取事務時,會處理不同的事務傳播行為,例如當前存在事務,但調用方法事務傳播級別為REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED時,對當前事務進行掛起、恢復等操作,以此保證了當前數據庫操作獲取正確的Connection。
具體是在子事務提交的最后會將掛起的事務恢復,恢復時重新調用TransactionSynchronizationManager. bindResource設置之前的connection holder,這樣再獲取的連接就是被恢復的數據庫連接, TransactionSynchronizationManager當前激活的連接只能是一個。
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的事務是通過AOP代理類中的一個Advice(TransactionInterceptor)進行生效的,而傳播級別定義了事務與子事務獲取連接、事務提交、回滾的具體方式。
AOP(Aspect Oriented Programming),即面向切面編程。Spring AOP技術實現上其實就是代理類,具體可分為靜態代理和動態代理兩大類,其中靜態代理是指使用 AOP 框架提供的命令進行編譯,從而在編譯階段就可生成 AOP 代理類,因此也稱為編譯時增強;(AspectJ);而動態代理則在運行時借助于 默寫類庫在內存中“臨時”生成 AOP 動態代理類,因此也被稱為運行時增強。其中java是使用的動態代理模式 (JDK+CGLIB)。
JDK動態代理 JDK動態代理主要涉及到java.lang.reflect包中的兩個類:Proxy和InvocationHandler。InvocationHandler是一個接口,通過實現該接口定義橫切邏輯,并通過反射機制調用目標類的代碼,動態將橫切邏輯和業務邏輯編制在一起。Proxy利用InvocationHandler動態創建一個符合某一接口的實例,生成目標類的代理對象。
CGLIB動態代理 CGLIB全稱為Code Generation Library,是一個強大的高性能,高質量的代碼生成類庫,可以在運行期擴展Java類與實現Java接口,CGLIB封裝了asm,可以再運行期動態生成新的class。和JDK動態代理相比較:JDK創建代理有一個限制,就是只能為接口創建代理實例,而對于沒有通過接口定義業務方法的類,則可以通過CGLIB創建動態代理。
搜索Java知音公眾號,回復“后端面試”,送你一份Java面試題寶典.pdf
CGLIB 創建代理的速度比較慢,但創建代理后運行的速度卻非???#xff0c;而 JDK 動態代理正好相反。如果在運行的時候不斷地用 CGLIB 去創建代理,系統的性能會大打折扣。因此如果有接口,Spring默認使用JDK 動態代理,源代碼如下:
public?class?DefaultAopProxyFactory?implements?AopProxyFactory,?Serializable?{@Overridepublic?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代理的兩種特點后,我們也就知道在做事務切面配置時的一些注意事項,例如JDK代理時方法必須是public,CGLIB代理時必須是public、protected,且類不能是final的;在依賴注入時,如果屬性類型定義為實現類,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代理時則會成功注入,所以如果有接口,建議注入時該類屬性都定義為接口。另外事務切點都配置在實現類和接口都可以生效,但建議加在實現類上。
官網關于Spring AOP的詳細介紹
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html%23aop
三、Spring事務的那些坑
通過之前章節,相信您已經掌握了spring事務的使用方式與原理,不過還是要注意,因為一不小心就可能就掉坑。首先看第一個坑:
3.1 事務不生效
測試代碼,事務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{ @Autowiredprivate?IAccountDao?accountDao;@Autowiredprivate?IStockDao?stockDao;@Overridepublic?void?openAccount(String?aname,?double?money)?{accountDao.insertAccount(aname,?money);}@Overridepublic?void?openStock(String?sname,?int?amount)?{stockDao.insertStock(sname,?amount);}@Overridepublic?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());} //調用同類方法,外圍配置事務public?void?openTx(String?aname,?double?money)?{openAccount(aname,money);openStock(aname,11);}1.運行輸出:
insertAccount connection hashcode=319558327
insertStock connection hashcode=319558327
2.運行輸出:
insertAccount connection hashcode=1333810223
insertStock connection hashcode=1623009085
3.運行輸出:
insertAccount connection hashcode=303240439
insertStock connection hashcode=303240439
可以看到2、3測試方法跟我們事務預期并一樣,結論:調用方法未配置事務、本類方法直接調用,事務都不生效!
究其原因,還是因為Spring的事務本質上是個代理類,而本類方法直接調用時其對象本身并不是織入事務的代理,所以事務切面并未生效。具體可以參見#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));}那如何修改為代理類調用呢?最直接的想法是注入自身,代碼如下:
????@Autowiredprivate?IStockProcessService?stockProcessService; //注入自身類,循環依賴,親測可以?public?void?openTx(String?aname,?double?money)?{stockProcessService.openAccount(aname,money);stockProcessService.openStockInAnotherDb?(aname,11);}當然Spring提供了獲取當前代理的方法:代碼如下:
//通過AopContext.currentProxy()方法獲取代理@Overridepublic?void?openWithMultiTx(String?aname,?double?money)?{ ((IStockProcessService)AopContext.currentProxy()).openAccount(aname,money);((IStockProcessService)AopContext.currentProxy()).openStockInAnotherDb(aname,?11);}另外Spring是通過TransactionSynchronizationManager類中線程變量來獲取事務中數據庫連接,所以如果是多線程調用或者繞過Spring獲取數據庫連接,都會導致Spring事務配置失效。
最后Spring事務配置失效的場景:
事務切面未配置正確
本類方法調用
多線程調用
繞開Spring獲取數據庫連接
接下來我們看下Spring的事務的另外一個坑:
3.2 事務不回滾
測試代碼:
<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);} ????@Testpublic?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);}輸出結果:
insertAccount connection hashcode=656479172
updateAccount connection hashcode=517355658
account balance is 8000.0
應用拋出異常,但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?{ @Overridepublic?boolean?rollbackOn(Throwable?ex)?{return?(ex?instanceof?RuntimeException?||?ex?instanceof?Error);} … }由代碼可見,Spring事務默認只對RuntimeException和Error進行回滾,如果應用需要對指定的異常類進行回滾,可配置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>事務不回滾的原因:
事務配置切面未生效
應用方法中將異常捕獲
拋出的異常不屬于運行時異常(例如IOException),
rollback-for屬性配置不正確
接下來我們看下Spring事務的第三個坑:
3.3 事務超時不生效
測試代碼:
<!--?注冊事務通知?--><tx:advice?id="txAdvice"?transaction-manager="myTxManager"><tx:attributes><tx:method?name="openAccountForLongTime"?isolation="DEFAULT"?propagation="REQUIRED"?timeout="3"/></tx:attributes></tx:advice> @Overridepublic?void?openAccountForLongTime(String?aname,?double?money)?{accountDao.insertAccount(aname,?money);try?{Thread.sleep(5000L);//在數據庫操作之后超時}?catch?(InterruptedException?e)?{e.printStackTrace();}} ????@Testpublic?void?testTimeout()?{service.openAccountForLongTime("dcbs",?10000);}正常運行,事務超時未生效
public?void?openAccountForLongTime(String?aname,?double?money)?{try?{Thread.sleep(5000L);?//在數據庫操作之前超時}?catch?(InterruptedException?e)?{e.printStackTrace();}accountDao.insertAccount(aname,?money);}拋出事務超時異常,超時生效
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事務超時的判斷機制:
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所調用, 繼續看applyTimeout的Call Hierarchy,可以看到有兩處調用,一個是JdbcTemplate,一個是TransactionAwareInvocationHandler類,后者是只有TransactionAwareDataSourceProxy類調用,該類為DataSource的事務代理類,我們一般并不會用到。難道超時只能在這調用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?blocke.printStackTrace();}return?0;}運行正常,事務超時失效
由上可見:Spring事務超時判斷在通過JdbcTemplate的數據庫操作時,所以如果超時后未有JdbcTemplate方法調用,則無法準確判斷超時。另外也可以得知,如果通過Mybatis等操作數據庫,Spring的事務超時是無效的。鑒于此,Spring的事務超時謹慎使用。
搜索Java知音公眾號,回復“后端面試”,送你一份Java面試題寶典.pdf
四、 總結
JDBC規范中Connection 的setAutoCommit是原生控制手動事務的方法,但傳播行為、異?;貪L、連接管理等很多技術問題都需要開發者自己處理,而Spring事務通過AOP方式非常優雅的屏蔽了這些技術復雜度,使得事務管理變的異常簡單。
但凡事有利弊,如果對實現機制理解不透徹,很容易掉坑里。最后總結下Spring事務的可能踩的坑:
1. ?Spring事務未生效
-
調用方法本身未正確配置事務
-
本類方法直接調用
-
數據庫操作未通過Spring的DataSourceUtils獲取Connection
-
多線程調用
2. ?Spring事務回滾失效
-
未準確配置rollback-for屬性
-
異常類不屬于RuntimeException與Error
-
應用捕獲了異常未拋出
3. ?Spring事務超時不準確或失效
-
超時發生在最后一次JdbcTemplate操作之后
-
通過非JdbcTemplate操作數據庫,例如Mybatis
推薦文章
-
硬剛一周,3W字總結,一年的經驗告訴你如何準備校招!
-
今年的校招,Java 好拿 offer 嗎?
-
10月了,該聊聊今年秋招了!
-
聊聊在騰訊實習快一個月的感受
總結
以上是生活随笔為你收集整理的Spring事务的那些坑,这里都给你总结好了!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我设计了一个牛逼的本地缓存!
- 下一篇: 跟前腾讯总监学Java实战项目