Spring 事務的那些坑,都在這裏了!

做者:蚊子squirrel
www.jianshu.com/p/a4229aa79acehtml

Spring框架已經是JAVA項目的標配,其中Spring事務管理也是最經常使用的一個功能,但若是不瞭解其實現原理,使用姿式不對,一不當心就可能掉坑裏。java

爲了更透徹的說明這些坑,本文分四部分展開闡述:spring

第一部分簡單介紹下Spring事務集成的幾種方式;sql

第二部分結合Spring源代碼說明Spring事務的實現原理;數據庫

第三部分經過實際測試代碼介紹關於Spring事務的坑;express

第四部分是對本文的總結。編程

1、Spring事務管理的幾種方式:

Spring事務在具體使用方式上可分爲兩大類:多線程

1. 聲明式

  • 基於 TransactionProxyFactoryBean的聲明式事務管理
  • 基於 <tx><aop> 命名空間的事務管理
  • 基於 @Transactional 的聲明式事務管理

2. 編程式

  • 基於事務管理器API 的編程式事務管理
  • 基於TransactionTemplate 的編程式事務管理

目前大部分項目使用的是聲明式的後兩種:intellij-idea

  • 基於 <tx><aop> 命名空間的聲明式事務管理能夠充分利用切點表達式的強大支持,使得管理事務更加靈活。
  • 基於 @Transactional 的方式須要實施事務管理的方法或者類上使用 @Transactional 指定事務規則便可實現事務管理,在Spring Boot中一般也建議使用這種註解方式來標記事務。

2、Spring事務實現機制

接下來咱們詳細看下Spring事務的源代碼,進而瞭解其工做原理。咱們從<tx>標籤的解析類開始:app

@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事務的核心實現類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() {
        @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;
    }
}

至此咱們瞭解事務的整個調用流程,但還有一個重要的機制沒分析到,那就是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。

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建立動態代理。

CGLIB 建立代理的速度比較慢,但建立代理後運行的速度卻很是快,而 JDK 動態代理正好相反。若是在運行的時候不斷地用 CGLIB 去建立代理,系統的性能會大打折扣。所以若是有接口,Spring默認使用JDK 動態代理,源代碼以下:

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代理的兩種特色後,咱們也就知道在作事務切面配置時的一些注意事項,例如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

3、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{

  @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());
  }
  
  //調用同類方法,外圍配置事務
  public void openTx(String aname, double money) {
        openAccount(aname,money);
        openStock(aname,11);
  }
  
}

1.運行輸出:

insertAccount connection hashcode=319558327
insertStock connection hashcode=319558327

//調用同類方法,外圍未配置事務
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

能夠看到二、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));
}

那如何修改成代理類調用呢?最直接的想法是注入自身,代碼以下:

@Autowired
private IStockProcessService stockProcessService;

//注入自身類,循環依賴,親測能夠 
public void openTx(String aname, double money) {
    stockProcessService.openAccount(aname,money);
    stockProcessService.openStockInAnotherDb (aname,11);
}

固然Spring提供了獲取當前代理的方法:代碼以下:

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

另外Spring是經過TransactionSynchronizationManager類中線程變量來獲取事務中數據庫鏈接,因此若是是多線程調用或者繞過Spring獲取數據庫鏈接,都會致使Spring事務配置失效。

最後Spring事務配置失效的場景:

  1. 事務切面未配置正確
  2. 本類方法調用
  3. 多線程調用
  4. 繞開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);
}

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

輸出結果:

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 {
@Override
    public 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>

事務不回滾的緣由:

  1. 事務配置切面未生效
  2. 應用方法中將異常捕獲
  3. 拋出的異常不屬於運行時異常(例如IOException),
  4. 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>
@Override
public void openAccountForLongTime(String aname, double money) {
    accountDao.insertAccount(aname, money);
    try {
        Thread.sleep(5000L);//在數據庫操做以後超時
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

@Test
public 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 block
        e.printStackTrace();
    }
    return 0;
}

運行正常,事務超時失效

由上可見:Spring事務超時判斷在經過JdbcTemplate的數據庫操做時,因此若是超時後未有JdbcTemplate方法調用,則沒法準確判斷超時。另外也能夠得知,若是經過Mybatis等操做數據庫,Spring的事務超時是無效的。鑑於此,Spring的事務超時謹慎使用。

4、 總結

JDBC規範中Connection 的setAutoCommit是原生控制手動事務的方法,但傳播行爲、異常回滾、鏈接管理等不少技術問題都須要開發者本身處理,而Spring事務經過AOP方式很是優雅的屏蔽了這些技術複雜度,使得事務管理變的異常簡單。

但凡事有利弊,若是對實現機制理解不透徹,很容易掉坑裏。最後總結下Spring事務的可能踩的坑:

1. Spring事務未生效

  • 調用方法自己未正確配置事務
  • 本類方法直接調用
  • 數據庫操做未經過Spring的DataSourceUtils獲取Connection
  • 多線程調用

2. Spring事務回滾失效

  • 未準確配置rollback-for屬性
  • 異常類不屬於RuntimeException與Error
  • 應用捕獲了異常未拋出

3. Spring事務超時不許確或失效

  • 超時發生在最後一次JdbcTemplate操做以後
  • 經過非JdbcTemplate操做數據庫,例如Mybatis

近期熱文推薦:

1.Java 15 正式發佈, 14 個新特性,刷新你的認知!!

2.終於靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!

3.我用 Java 8 寫了一段邏輯,同事直呼看不懂,你試試看。。

4.吊打 Tomcat ,Undertow 性能很炸!!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

以爲不錯,別忘了隨手點贊+轉發哦!

相關文章
相關標籤/搜索