激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專(zhuān)注于服務(wù)器技術(shù)及軟件下載分享
分類(lèi)導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語(yǔ)言|JavaScript|易語(yǔ)言|vb.net|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - Spring事務(wù)的那些坑,這里都給你總結(jié)好了!

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

2020-11-24 23:42Java知音 Java教程

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

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ù)在具體使用方式上可分為兩大類(lèi):

1.  聲明式

  •  基于 TransactionProxyFactoryBean的聲明式事務(wù)管理
  •  基于 <tx> 和 <aop> 命名空間的事務(wù)管理
  •  基于 @Transactional 的聲明式事務(wù)管理

2.  編程式

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

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

  •  基于 <tx> 和 <aop> 命名空間的聲明式事務(wù)管理可以充分利用切點(diǎn)表達(dá)式的強(qiáng)大支持,使得管理事務(wù)更加靈活。
  •  基于 @Transactional 的方式需要實(shí)施事務(wù)管理的方法或者類(lèi)上使用 @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)而了解其工作原理。我們從<tx>標(biāo)簽的解析類(lèi)開(kāi)始:

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

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

TransactionInterceptor:

  1. public Object invoke(final MethodInvocation invocation) throws Throwable {  
  2.         Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);  
  3.         // Adapt to TransactionAspectSupport's invokeWithinTransaction...  
  4.         return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {  
  5.             @Override  
  6.             public Object proceedWithInvocation() throws Throwable {  
  7.                 return invocation.proceed();  
  8.             }  
  9.         });  
  10.     } 

TransactionAspectSupport

  1. protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)  
  2.             throws Throwable {  
  3.         // If the transaction attribute is null, the method is non-transactional.  
  4.         final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);  
  5.         final PlatformTransactionManager tm = determineTransactionManager(txAttr);  
  6.         final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);  
  7.         if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {  
  8.             // Standard transaction demarcation with getTransaction and commit/rollback calls.  
  9.             TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);  
  10.             Object retVal = null 
  11.             try {  
  12.                 // This is an around advice: Invoke the next interceptor in the chain.  
  13.                 // This will normally result in a target object being invoked.  
  14.                 retVal = invocation.proceedWithInvocation();  
  15.             }  
  16.             catch (Throwable ex) {  
  17.                 // target invocation exception  
  18.                 completeTransactionAfterThrowing(txInfo, ex);  
  19.                 throw ex;  
  20.             }  
  21.             finally {  
  22.                 cleanupTransactionInfo(txInfo);  
  23.             }  
  24.             commitTransactionAfterReturning(txInfo);  
  25.             return retVal;  
  26.         } 
  27.  

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

  1. public abstract class DataSourceUtils {  
  2. …  
  3. public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {  
  4.         try {  
  5.             return doGetConnection(dataSource);  
  6.         }  
  7.         catch (SQLException ex) { 
  8.              throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);  
  9.         }  
  10.     }  
  11. public static Connection doGetConnection(DataSource dataSource) throws SQLException {  
  12.         Assert.notNull(dataSource, "No DataSource specified"); 
  13.         ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);  
  14.         if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {  
  15.             conHolder.requested();  
  16.             if (!conHolder.hasConnection()) {  
  17.                 logger.debug("Fetching resumed JDBC Connection from DataSource");  
  18.                 conHolder.setConnection(dataSource.getConnection());  
  19.             }  
  20.             return conHolder.getConnection();  
  21.         }  
  22. …  

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

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

TransactionSynchronizationManager

  1. private static final ThreadLocal<Map<Object, Object>> resources =  
  2.             new NamedThreadLocal<Map<Object, Object>>("Transactional resources");  
  3. …  
  4. public static Object getResource(Object key) {  
  5.         Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);  
  6.         Object value = doGetResource(actualKey);  
  7.         if (value != null && logger.isTraceEnabled()) {  
  8.             logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +  
  9.                     Thread.currentThread().getName() + "]");  
  10.         }  
  11.         return value;  
  12.     }  
  13.     /**  
  14.      * Actually check the value of the resource that is bound for the given key.  
  15.      */  
  16.     private static Object doGetResource(Object actualKey) {  
  17.         Map<Object, Object> map = resources.get();  
  18.         if (map == null) {  
  19.             return null;  
  20.         }  
  21.         Object value = map.get(actualKey);  
  22.         // Transparently remove ResourceHolder that was marked as void...  
  23.         if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {  
  24.             map.remove(actualKey);  
  25.             // Remove entire ThreadLocal if empty...  
  26.             if (map.isEmpty()) {  
  27.                 resources.remove();  
  28.             }  
  29.             value = null
  30.          } 
  31.          return value;  
  32.     } 

在事務(wù)管理器類(lèi)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

  1. private TransactionStatus handleExistingTransaction(  
  2.            TransactionDefinition definition, Object transaction, boolean debugEnabled)  
  3.            throws TransactionException {  
  4.        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {  
  5.            if (debugEnabled) {  
  6.                logger.debug("Suspending current transaction, creating new transaction with name [" +  
  7.                        definition.getName() + "]");  
  8.            }  
  9.            SuspendedResourcesHolder suspendsuspendedResources = suspend(transaction);  
  10.            try {  
  11.                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);  
  12.                DefaultTransactionStatus status = newTransactionStatus 
  13.                        definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);  
  14.                doBegin(transaction, definition);  
  15.                prepareSynchronization(status, definition); 
  16.                 return status;  
  17.            }  
  18.            catch (RuntimeException beginEx) {  
  19.                resumeAfterBeginException(transaction, suspendedResources, beginEx);  
  20.                throw beginEx;  
  21.            }  
  22.            catch (Error beginErr) {  
  23.                resumeAfterBeginException(transaction, suspendedResources, beginErr);  
  24.                throw beginErr;  
  25.            } 
  26.         }  
  27. **  
  28.     * Clean up after completion, clearing synchronization if necessary,  
  29.     * and invoking doCleanupAfterCompletion.  
  30.     * @param status object representing the transaction  
  31.     * @see #doCleanupAfterCompletion  
  32.     */  
  33.    private void cleanupAfterCompletion(DefaultTransactionStatus status) {  
  34.        status.setCompleted();  
  35.        if (status.isNewSynchronization()) {  
  36.            TransactionSynchronizationManager.clear();  
  37.        }  
  38.        if (status.isNewTransaction()) {  
  39.            doCleanupAfterCompletion(status.getTransaction());  
  40.        }  
  41.        if (status.getSuspendedResources() != null) { 
  42.             if (status.isDebug()) {  
  43.                logger.debug("Resuming suspended transaction after completion of inner transaction");  
  44.            }  
  45.            resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources());  
  46.        }  
  47.    } 

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

AOP(Aspect Oriented Programming),即面向切面編程。Spring AOP技術(shù)實(shí)現(xiàn)上其實(shí)就是代理類(lèi),具體可分為靜態(tài)代理和動(dòng)態(tài)代理兩大類(lèi),其中靜態(tài)代理是指使用 AOP 框架提供的命令進(jìn)行編譯,從而在編譯階段就可生成 AOP 代理類(lèi),因此也稱(chēng)為編譯時(shí)增強(qiáng);(AspectJ);而動(dòng)態(tài)代理則在運(yùn)行時(shí)借助于 默寫(xiě)類(lèi)庫(kù)在內(nèi)存中“臨時(shí)”生成 AOP 動(dòng)態(tài)代理類(lèi),因此也被稱(chēng)為運(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è)類(lèi):Proxy和InvocationHandler。InvocationHandler是一個(gè)接口,通過(guò)實(shí)現(xiàn)該接口定義橫切邏輯,并通過(guò)反射機(jī)制調(diào)用目標(biāo)類(lèi)的代碼,動(dòng)態(tài)將橫切邏輯和業(yè)務(wù)邏輯編制在一起。Proxy利用InvocationHandler動(dòng)態(tài)創(chuàng)建一個(gè)符合某一接口的實(shí)例,生成目標(biāo)類(lèi)的代理對(duì)象。

CGLIB動(dòng)態(tài)代理 CGLIB全稱(chēng)為Code Generation Library,是一個(gè)強(qiáng)大的高性能,高質(zhì)量的代碼生成類(lèi)庫(kù),可以在運(yùn)行期擴(kuò)展Java類(lèi)與實(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ù)方法的類(lèi),則可以通過(guò)CGLIB創(chuàng)建動(dòng)態(tài)代理。

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

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)代理,源代碼如下:

  1. public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {  
  2.     @Override  
  3.     public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {  
  4.         if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {  
  5.             Class<?> targetClass = config.getTargetClass();  
  6.             if (targetClass == null) {  
  7.                 throw new AopConfigException("TargetSource cannot determine target class: " +  
  8.                         "Either an interface or a target is required for proxy creation.");  
  9.             }  
  10.             if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {  
  11.                 return new JdkDynamicAopProxy(config);  
  12.             }  
  13.             return new ObjenesisCGLIBAopProxy(config);  
  14.         }     
  15.         else {  
  16.             return new JdkDynamicAopProxy(config);  
  17.         }  
  18.     }  

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

  1. 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í)該類(lèi)屬性都定義為接口。另外事務(wù)切點(diǎn)都配置在實(shí)現(xiàn)類(lèi)和接口都可以生效,但建議加在實(shí)現(xiàn)類(lèi)上。

官網(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)橐徊恍⌒木涂赡芫偷艨印J紫瓤吹谝粋€(gè)坑:

3.1 事務(wù)不生效

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

  1. <tx:advice id="txAdvice" transaction-manager="myTxManager">  
  2.         <tx:attributes>  
  3.             <!-- 指定在連接點(diǎn)方法上應(yīng)用的事務(wù)屬性 -->  
  4.             <tx:method name="openAccount" isolation="DEFAULT" propagation="REQUIRED"/>  
  5.             <tx:method name="openStock" isolation="DEFAULT" propagation="REQUIRED"/>  
  6.             <tx:method name="openStockInAnotherDb" isolation="DEFAULT" propagation="REQUIRES_NEW"/>  
  7.             <tx:method name="openTx" isolation="DEFAULT" propagation="REQUIRED"/>  
  8.             <tx:method name="openWithoutTx" isolation="DEFAULT" propagation="NEVER"/>  
  9.             <tx:method name="openWithMultiTx" isolation="DEFAULT" propagation="REQUIRED"/>  
  10. </tx:advice> 
  1. public class StockProcessServiceImpl implements IStockProcessService{  
  2. @Autowired  
  3.      private IAccountDao accountDao;  
  4.     @Autowired  
  5.      private IStockDao stockDao;     
  6.      @Override  
  7.     public void openAccount(String aname, double money) {  
  8.         accountDao.insertAccount(aname, money);  
  9.     }  
  10.     @Override  
  11.     public void openStock(String sname, int amount) {  
  12.         stockDao.insertStock(sname, amount);  
  13.     }     
  14.     @Override  
  15.     public void openStockInAnotherDb(String sname, int amount) {  
  16.         stockDao.insertStock(sname, amount);  
  17.  
  18.  
  19. public void insertAccount(String aname, double money) {  
  20.         String sql = "insert into account(aname, balance) values(?,?)" 
  21.         this.getJdbcTemplate().update(sql, aname, money);  
  22.         DbUtils.printDBConnectionInfo("insertAccount",getDataSource());  
  23. }   
  24.     public void insertStock(String sname, int amount) {  
  25.         String sql = "insert into stock(sname, count) values (?,?)" 
  26.         this.getJdbcTemplate().update(sql , sname, amount);  
  27.         DbUtils.printDBConnectionInfo("insertStock",getDataSource());  
  28.  
  29.     public static void printDBConnectionInfo(String methodName,DataSource ds) {  
  30.         Connection connection = DataSourceUtils.getConnection(ds);  
  31.         System.out.println(methodName+" connection hashcode="+connection.hashCode());  
  32.     } 
  1. //調(diào)用同類(lèi)方法,外圍配置事務(wù)  
  2.     public void openTx(String aname, double money) {  
  3.             openAccount(aname,money);  
  4.             openStock(aname,11);  
  5.     } 

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

insertAccount connection hashcode=319558327

insertStock connection hashcode=319558327

  1. //調(diào)用同類(lèi)方法,外圍未配置事務(wù)  
  2.     public void openWithoutTx(String aname, double money) {  
  3.         openAccount(aname,money);  
  4.             openStock(aname,11);  
  5.     } 

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

insertAccount connection hashcode=1333810223

insertStock connection hashcode=1623009085

  1. //通過(guò)AopContext.currentProxy()方法獲取代理  
  2. @Override  
  3. public void openWithMultiTx(String aname, double money) {  
  4. openAccount(aname,money);    
  5. 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ù)、本類(lèi)方法直接調(diào)用,事務(wù)都不生效!

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

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

  1. public static void printProxyInfo(Object bean) {  
  2.         System.out.println("isAopProxy"+AopUtils.isAopProxy(bean));  
  3.         System.out.println("isCGLIBProxy="+AopUtils.isCGLIBProxy(bean));  
  4.         System.out.println("isJdkProxy="+AopUtils.isJdkDynamicProxy(bean));  
  5.     } 

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

  1.     @Autowired  
  2.     private IStockProcessService stockProcessService;  
  3. //注入自身類(lèi),循環(huán)依賴(lài),親測(cè)可以   
  4.     public void openTx(String aname, double money) {  
  5.             stockProcessService.openAccount(aname,money);  
  6.             stockProcessService.openStockInAnotherDb (aname,11); 
  7.     } 

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

  1. //通過(guò)AopContext.currentProxy()方法獲取代理  
  2.     @Override  
  3.     public void openWithMultiTx(String aname, double money) {  
  4. ((IStockProcessService)AopContext.currentProxy()).openAccount(aname,money);  
  5. ((IStockProcessService)AopContext.currentProxy()).openStockInAnotherDb(aname, 11);  
  6.     } 

另外Spring是通過(guò)TransactionSynchronizationManager類(lèi)中線程變量來(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.  本類(lèi)方法調(diào)用
  3.  多線程調(diào)用
  4.  繞開(kāi)Spring獲取數(shù)據(jù)庫(kù)連接

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

3.2 事務(wù)不回滾

測(cè)試代碼:

  1. <tx:advice id="txAdvice" transaction-manager="myTxManager">  
  2.         <tx:attributes>  
  3.             <!-- 指定在連接點(diǎn)方法上應(yīng)用的事務(wù)屬性 -->  
  4.             <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED"/>  
  5.         </tx:attributes>  
  6. </tx:advice> 
  1. public void buyStock(String aname, double money, String sname, int amount) throws StockException {  
  2.         boolean isBuy = true 
  3.         accountDao.updateAccount(aname, money, isBuy);  
  4.         // 故意拋出異常  
  5.         if (true) {  
  6.             throw new StockException("購(gòu)買(mǎi)股票異常");  
  7.         }  
  8.         stockDao.updateStock(sname, amount, isBuy);  
  9.     }    
  1. @Test  
  2.    public void testBuyStock() {  
  3.        try {  
  4.            service.openAccount("dcbs", 10000);  
  5.            service.buyStock("dcbs", 2000, "dap", 5);  
  6.        } catch (StockException e) {  
  7.            e.printStackTrace();  
  8.        }  
  9.        double accountBalance = service.queryAccountBalance("dcbs");  
  10.        System.out.println("account balance is " + accountBalance);  
  11.    } 

輸出結(jié)果:

insertAccount connection hashcode=656479172

updateAccount connection hashcode=517355658

account balance is 8000.0

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

TransactionAspectSupport

  1. protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {  
  2.         if (txInfo != null && txInfo.hasTransaction()) {  
  3.             if (logger.isTraceEnabled()) {  
  4.                 logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +  
  5.                         "] after exception: " + ex);  
  6.             }  
  7.             if (txInfo.transactionAttribute.rollbackOn(ex)) {  
  8.                 try {  
  9.                     txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());  
  10.                 }  
  11.                 catch (TransactionSystemException ex2) {  
  12.                     logger.error("Application exception overridden by rollback exception", ex);  
  13.                     ex2.initApplicationException(ex);  
  14.                     throw ex2;  
  15.                 }  
  16.                 …  
  17.  
  18. public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute {  
  19. @Override  
  20.     public boolean rollbackOn(Throwable ex) {  
  21.         return (ex instanceof RuntimeException || ex instanceof Error);  
  22.     }  
  23. … 

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

  1. <!-- 注冊(cè)事務(wù)通知 -->  
  2.    <tx:advice id="txAdvice" transaction-manager="myTxManager">  
  3.        <tx:attributes>  
  4.            <!-- 指定在連接點(diǎn)方法上應(yīng)用的事務(wù)屬性 -->  
  5.            <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="StockException"/>  
  6.        </tx:attributes>  
  7.    </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è)試代碼:

  1. <!-- 注冊(cè)事務(wù)通知 -->  
  2.     <tx:advice id="txAdvice" transaction-manager="myTxManager">  
  3.         <tx:attributes> 
  4.               <tx:method name="openAccountForLongTime" isolation="DEFAULT" propagation="REQUIRED" timeout="3"/>  
  5.         </tx:attributes>  
  6.     </tx:advice> 
  1. @Override  
  2.     public void openAccountForLongTime(String aname, double money) {  
  3.         accountDao.insertAccount(aname, money);  
  4.         try {  
  5.             Thread.sleep(5000L);//在數(shù)據(jù)庫(kù)操作之后超時(shí)  
  6.         } catch (InterruptedException e) {  
  7.             e.printStackTrace();  
  8.         }  
  9.     } 
  1. @Test  
  2. public void testTimeout() {  
  3.     service.openAccountForLongTime("dcbs", 10000);  

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

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

拋出事務(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

  1. /**  
  2.      * Return the time to live for this object in milliseconds.  
  3.      * @return number of millseconds until expiration  
  4.      * @throws TransactionTimedOutException if the deadline has already been reached  
  5.      */  
  6.     public long getTimeToLiveInMillis() throws TransactionTimedOutException{  
  7.         if (this.deadline == null) {  
  8.             throw new IllegalStateException("No timeout specified for this resource holder");  
  9.         }  
  10.         long timeToLive = this.deadline.getTime() - System.currentTimeMillis();  
  11.         checkTransactionTimeout(timeToLive <= 0);  
  12.         return timeToLive;  
  13.     }  
  14.     /**  
  15.      * Set the transaction rollback-only if the deadline has been reached,  
  16.      * and throw a TransactionTimedOutException.  
  17.      */  
  18.     private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {  
  19.         if (deadlineReached) {  
  20.             setRollbackOnly();  
  21.             throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);  
  22.         }  
  23.     } 

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

  1. <!-- 注冊(cè)事務(wù)通知 -->  
  2.  <tx:advice id="txAdvice" transaction-manager="myTxManager">  
  3.      <tx:attributes>  
  4.          <tx:method name="openAccountForLongTimeWithoutJdbcTemplate" isolation="DEFAULT" propagation="REQUIRED" timeout="3"/>  
  5.      </tx:attributes>  
  6.  </tx:advice>    
  1. public void openAccountForLongTimeWithoutJdbcTemplate(String aname, double money) {  
  2.        try {  
  3.            Thread.sleep(5000L);  
  4.        } catch (InterruptedException e) {  
  5.            e.printStackTrace();  
  6.        }  
  7.        accountDao.queryAccountBalanceWithoutJdbcTemplate(aname);  
  8.    }  
  9.    public double queryAccountBalanceWithoutJdbcTemplate(String aname) {  
  10.           String sql = "select balance from account where aname = ?" 
  11.           PreparedStatement prepareStatement;  
  12.        try {  
  13.            prepareStatement = this.getConnection().prepareStatement(sql);  
  14.               prepareStatement.setString(1, aname);  
  15.               ResultSet executeQuery = prepareStatement.executeQuery();  
  16.               while(executeQuery.next()) {  
  17.                   return executeQuery.getDouble(1);  
  18.               }  
  19.        } catch (CannotGetJdbcConnectionException | SQLException e) {  
  20.            // TODO Auto-generated catch block  
  21.            e.printStackTrace();  
  22.        }  
  23.        return 0;  
  24.    } 

運(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)慎使用。

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

四、 總結(jié)

JDBC規(guī)范中Connection 的setAutoCommit是原生控制手動(dòng)事務(wù)的方法,但傳播行為、異常回滾、連接管理等很多技術(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ù)
  •  本類(lèi)方法直接調(diào)用
  •  數(shù)據(jù)庫(kù)操作未通過(guò)Spring的DataSourceUtils獲取Connection
  •  多線程調(diào)用

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

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

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

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

原文地址:https://mp.weixin.qq.com/s/etQvWD76XNs4ciZXnJ5TjQ

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产人成精品综合欧美成人 | 狠狠干夜夜操 | 欧美日韩免费在线观看视频 | 久草在线高清视频 | 一级做受毛片免费大片 | 黄色网大全 | 91中文字幕在线观看 | 视频h在线| 一区二区三区国产在线 | 亚洲精品久久久久久 | 美女喷水网站 | 黄色免费不卡视频 | 黄色免费在线电影 | 久久精国| 欧美日在线观看 | 欧美一级色片 | www国产成人免费观看视频,深夜成人网 | 国产精品剧情一区二区在线观看 | 一级免费观看 | 深夜影院a | 亚洲成人欧美在线 | 国产一区二区精品在线观看 | 一区二区三区欧洲 | 久久成人福利 | 天天艹综合 | 爱草成年 | 免费观看视频在线 | 中文字幕精品在线观看 | 久久精品日产高清版的功能介绍 | 精品一区二区三区免费毛片爱 | 91久久极品少妇韩国 | 欧美特一级 | 国人精品视频在线观看 | 中文字幕综合在线观看 | 久久久久久久久亚洲精品 | 92精品国产自产在线 | 国产羞羞视频免费在线观看 | 精品中文字幕视频 | 久久综合久久精品 | 7777久久香蕉成人影院 | 亚洲五码在线观看视频 |