Spring事務的那些坑,這裡都給你總結好了!

2021-02-15 猿不理架構師

Spring框架已是JAVA項目的標配,其中Spring事務管理也是最常用的一個功能,但如果不了解其實現原理,使用姿勢不對,一不小心就可能掉坑裡。

為了更透徹的說明這些坑,本文分四部分展開闡述:第一部分簡單介紹下Spring事務集成的幾種方式;第二部分結合Spring原始碼說明Spring事務的實現原理;第三部分通過實際測試代碼介紹關於Spring事務的坑;第四部分是對本文的總結。

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

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

1.  聲明式

基於 TransactionProxyFactoryBean的聲明式事務管理

基於 <tx> 和 <aop> 命名空間的事務管理

基於 @Transactional 的聲明式事務管理

2.  編程式

基於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 {
    @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。

搜索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 創建代理的速度比較慢,但創建代理後運行的速度卻非常快,而 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

三、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

可以看到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));
    }

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

    @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事務配置失效的場景:

接下來我們看下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>

事務不回滾的原因:

拋出的異常不屬於運行時異常(例如IOException),

接下來我們看下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的事務超時謹慎使用。

搜索Java知音公眾號,回復「後端面試」,送你一份Java面試題寶典.pdf

四、 總結

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

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

1.  Spring事務未生效資料庫操作未通過Spring的DataSourceUtils獲取Connection2.  Spring事務回滾失效異常類不屬於RuntimeException與Error3.  Spring事務超時不準確或失效超時發生在最後一次JdbcTemplate操作之後通過非JdbcTemplate操作資料庫,例如Mybatis

相關焦點

  • Spring 事務的那些坑,都在這裡了!
    第三部分通過實際測試代碼介紹關於Spring事務的坑;第四部分是對本文的總結。官網關於Spring AOP的詳細介紹https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html%23aop三、Spring事務的那些坑通過之前章節,相信您已經掌握了spring
  • Spring web MVC 之 @RequestParam和@RequestBody
    有人要問我為什麼或者怎麼想到 舉這樣一個例子呢,很明顯,我看到網上很多人都複製一個案例,來介紹@RequestParam和@RequestBody這兩個參數,在這裡只是想來糾正這一錯誤的說法。好了,廢話不多說我們首先來看看在spring web  mvc中@RequestParam和@RequestBody如何實現解析的。
  • 與同事關係再好,也不要隨便透露這3件事,小心被坑
    如有侵權,請在後臺留言聯繫我們進行刪除,謝謝與同事關係再好,也不要隨便透露這3件事,小心被坑大家想必都知道溝通對於我們生活和工作的重要性,也都聽過在職場上,要和同事處好關係的做法,因此,除去那些實在是不善於社交的人,大多數人都會選擇和同事打好關係。而當大家關係好的時候,平日裡多多少少會通過聊天的方式,拉近雙方的感情,也可以交換彼此的信息。
  • 那些年網賺路上碰上的坑
    走心筆記,走的是心 我不知道你對網賺有多少了解 但我可以告訴你,依靠網際網路賺錢的坑很多、
  • 被你坑過的人都不是傻X
    伊芙利特之祭——被你坑過的人都不是傻X文/JASON SMOAK先講一個最近發生的事。前不久,有人上門來宣傳,拿著一張小卡片,面色柔和,說哥們我這是新開張的餐館,歡迎訂餐。這種簡單粗暴式的廣告向來會直接跟垃圾桶見面,只是感覺這小哥還算不錯,遂放起來了。
  • Spring Batch入門篇
    pom.xml 添加<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-batch</artifactId></dependency>
  • 為什麼越熟悉的人,越會坑你?
    為什麼越熟悉的人,越會坑你?今天,裝修直播間的團隊終於來了。這個團隊,是朋友推薦的專業團隊,也在業內小有名氣。
  • Java 開發中如何正確踩坑
    我一直都認為研發本身是很有創造性的,如果人不放鬆,或不夠聰明,都很難做得好。你要找到最好的人,一個好的工程師不是頂10個,是頂100個。所以,在核心工程師上面,大家一定要不惜血本去找,千萬不要想偷懶只用培養大學生的方法去做。最好的人本身有很強的驅動力,你只要把他放到他喜歡的事情上,讓他自己有玩的心態,他才能真正做出一些事情,打動他自己,才能打動別人。
  • 不吹不擂,你想要的Python面試都在這裡了【315+道題】
    36、至少列舉8個常用模塊都有那些?37、re的match和search區別?38、什麼是正則的貪婪匹配?第三部分 資料庫和緩存(46題)1、列舉常見的關係型資料庫和非關係型都有那些?2、MySQL常見資料庫引擎及比較?3、簡述數據三大範式?4、什麼是事務?MySQL如何支持事務?5、簡述資料庫設計中一對多和多對多的應用場景?6、如何基於資料庫實現商城商品計數器?
  • 手把手教你寫好法語CV(內附模板)
    而在這裡,小編也跟大家分享一下哪些詞是招聘者喜歡看到的,哪些是招聘者看太多而厭煩了的詞彙。,這表明招聘者更希望看到的是應聘者在之前的實習或者工作經驗中主要負責的事務及所作出的成果。大家千萬要記得避開以下這些坑哦: #1 "le meilleur" (最好的) #2 "un battant" (不服輸的人) #3 "sortir des sentiers battus" (另闢蹊徑) #4 "synergie" (協同作用) #5 "avenant" (討人喜歡的) #6 "être un leader réfléchi" (審慎的領導者
  • 拒絕「退坑」 | 五個方法讓你繼續彈下去!
    恭喜你,這是一個很酷的決定!但是否能持之以恆,對初學者來說才是成敗的關鍵。根據數據統計,那些「退坑」的吉他初學者,往往是在學習了3個月左右時選擇放棄的。當然,每個人都有各自不同的原因,但以下的5條建議會讓你更長久的學習下去:1.面對困難,不要放棄不要迴避困難,這會讓你成為更好的自己。
  • 能者多勞,到底是不是坑?
    我們每個人都認識-隔。壁。老。王。你不是沒精力照顧你的家庭嘛,那我來啊~在這方面我是能者,我有義務。只允許自己越來越好,不允許別人慢慢變好。其次,能者多勞是對企業的不負責。領導都喜歡只要1份錢就能幹8個人活兒的員工。但對於公司來說,這樣的用人策略很!低!級!
  • 論處理日常事務過程速率測算意識
    首先是我們處理日常事務,都是有一定工作量的。當然由於我們處理日常事務的方法或者工具不同,那麼工作總量是會有一定差異的,但總的來說是一定會有一定的工作量的,在方法或者工具沒有層次或者代際差異時,工作總量不會有數量級層面的差異。其次是做一件事情,是需要花一定時間的。
  • 茶飲加盟的那些坑,你踩到了幾個?
    然而,茶飲關店數量也在正比增長,並非所有的茶飲品牌都能稱為「品牌」。在網絡上搜索奶茶實驗室,你會得到數個「不同」的「奶茶實驗室」。本就是衝著網紅茶的人氣而去,卻加盟了一個「假」網紅回來,也是一些創業者失敗的一大原因。
  • 電話面試的坑,HR你都避開了嗎?
    3、紙與筆,能夠及時記錄自己對求職者面試的狀態。 4、按照自己所需,準備其他材料。
  • 學習避雷不踩坑,2021最強避坑指南
    假期一定有許多的同學們是晚上不睡覺早上不起床的狀態作息不規律宵夜不間斷刷劇、遊戲、逛街、天天在家葛優躺始終處在一個非常興奮和隨心所欲的狀態不要慌胖虎帶來最全避雷指南萬坑不離其中讓你新學期完美避開所有坑作為一個Accounting專業畢業的胖虎來說
  • 職場英語你入「坑」了嗎?
    你是否遇到過以下場景:某一天開會中,大家一起來討論一下某個案例,我們「勇敢」的助理AA脫口而出,Let's discuss
  • 有關鍊表的小技巧,都給你總結好了
    文章之前已經寫過了,點擊這裡直達複習。今天我們來說鍊表中最主要的 2 個技巧:雙指針法和 dummy node,相信看完本文後,鍊表相關的絕大多數題目你都能搞定啦。算法題都是工業生產中一些問題的抽象。比如說我們找中點的目的是為了把這個鍊表斷開,那麼返回了 3,我可以斷開 3 和 4;但是返回了 4,單向鍊表我怎麼斷開 4 之前的地方呢?還得再來一遍,麻煩。