Spring 事務回滾機制詳解


1:事務原理

1.1:aop/動態代理

類路徑:org/springframework/aop/framework/CglibAopProxy.java

drawing

ReflectiveMethodInvocation#proceed 后續:

drawing

作用:采用aop/動態代理的作用是為了在調用@Transactional 注解修飾的方法之前,對目標方法做一次增強。


1.2:threadLocal

drawing

作用:采用ThreadLocal的作用是用來存儲當前線程的事務節點信息。


1.3:事務核心代碼

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

    /**
     *  每個被 @Transactional 修飾的方法都會走一遍 transaction interceptor,然后新增一個事務節點。
     *  每個事務節點執行前都會判斷是否需要新建事務、開啟事務。
     **/
    @Nullable
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
            final InvocationCallback invocation) throws Throwable {

        // If the transaction attribute is null, the method is non-transactional.
        TransactionAttributeSource tas = getTransactionAttributeSource();
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            // 創建一個事務信息對象,每一次調用 @Transactional 注解修飾的方法,都會重新創建一個 TransactionInfo 對象。
            // 若有調用鏈有多個 @TransactionInfo 修飾的方法,則會形成一個事務鏈。
            // 將最新的事務節點信息通過 threadLocal 更新到當前線程 。
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

            Object retVal;
            try {
                // 真正執行crud語句的過程
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                // 拋異常之后決定是否回滾還是繼續提交
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                // 清除當前節點的事務信息,將舊事務節點信息通過 threadLocal 更新到當前線程。
                cleanupTransactionInfo(txInfo);
            }
            // 事務鏈執行完畢之后
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }else {
            ...
        }
    }

org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo

    protected final class TransactionInfo {

        // 事務管理器
        @Nullable
        private final PlatformTransactionManager transactionManager;

        @Nullable
        private final TransactionAttribute transactionAttribute;

        // 切點信息(類路徑#方法名稱)
        private final String joinpointIdentification;

        // 當前事務節點狀態(是否完成、)
        @Nullable
        private TransactionStatus transactionStatus;

        // 舊事務節點/前事務節點
        @Nullable
        private TransactionInfo oldTransactionInfo;
    }

1.4:事務鏈-圖

drawing

2:事務回滾場景

用兩個Service進行測試:

/**
 * 模擬 Service A
 **/
@Service
public class AopiService {
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Resource(name = AopiRepositry.PACKAGE_BEAN_NAME)
    private AopiRepositry aopiRepositry;
    @Resource
    private PmsTestService pmsTestService;
    @Resource
    private AopiService aopiService;

    ...
}

/**
 * 模擬 Service B
 **/
@Service
public class PmsTestService {

    @Transactional(rollbackFor = Exception.class)
    public void insertWithTransactional() {
        int i = 1 / 0;
    }

    public void insertWithoutTransactional() {
        int i = 1 / 0;
    }
}

模擬場景:

1:模擬ServiceA調用ServiceB(會異常,被try-catch),這種情況會回滾嗎?

2:模擬ServiceA中調用自己的本類中的方法(會異常,被try-catch),這種情況會回滾嗎?

3:模擬ServiceA注入自己並通過依賴的ServiceA調用另一個方法(會異常,被try-catch),這種情況會回滾嗎?


2.1:場景1-1

    /**
     * serviceA 加了 @Transactional
     * serviceB 加了 @Transactional
     * 最終:回滾
     **/
    @Transactional(rollbackFor = Exception.class)
    public void insertA() {
        Aopi aopi = new Aopi();
        aopi.setName("1");
        aopi.setAge(23);
        aopiRepositry.insert(aopi);
        try {
            pmsTestService.insertWithTransactional();
        } catch (Exception e) {
            log.error("插入報錯", e);
        }
        // 判斷事務是否回滾
        if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {
            log.info("事務回滾了");
        } else {
            log.info("事務沒回滾");
        }
    }

2.2:場景1-2

    /**
     * serviceA 加了 @Transactional
     * serviceB 沒加 @Transactional,但是手動 throw e;
     * 最終:回滾
     **/
    @Transactional(rollbackFor = Exception.class)
    public void insertAA() {
        Aopi aopi = new Aopi();
        aopi.setName("1");
        aopi.setAge(23);
        aopiRepositry.insert(aopi);
        try {
            pmsTestService.insertWithoutTransactional();
        } catch (Exception e) {
            log.error("插入報錯", e);
            throw e;
        }
    }

2.3:場景1-3

    /**
     * serviceA 加了 @Transactional
     * serviceB 沒加 @Transactional,但是手動 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
     * 最終:回滾
     * <p>
     * 若不手動 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),那么不會回滾
     **/
    @Transactional(rollbackFor = Exception.class)
    public void insertAAA() {
        Aopi aopi = new Aopi();
        aopi.setName("1");
        aopi.setAge(23);
        aopiRepositry.insert(aopi);
        try {
            pmsTestService.insertWithoutTransactional();
        } catch (Exception e) {
            log.error("插入報錯", e);
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        // 判斷事務是否回滾
        if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {
            log.info("事務回滾了");
        } else {
            log.info("事務沒回滾");
        }
    }

2.4:場景2-1

    /**
     * serviceA 加了 @Transactional
     * 調用過程中被異常被捕獲,並不拋出
     * 最終:不回滾
     **/
    @Transactional(rollbackFor = Exception.class)
    public void insertAAAA() {
        Aopi aopi = new Aopi();
        aopi.setName("1");
        aopi.setAge(23);
        aopiRepositry.insert(aopi);
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            log.error("插入報錯", e);
        }
        // 判斷事務是否回滾
        if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {
            log.info("事務回滾了");
        } else {
            log.info("事務沒回滾");
        }
    }

2.5:場景2-2

    /**
     * 本類方法A 加了 @Transactional
     * 本類方法B 加了 @Transactional,異常被捕獲,並不拋出
     * 最終:不回滾
     * <p>
     * 原因:調用 insert 並不會重新走代理調用(this 對象不是代理對象)
     **/
    @Transactional(rollbackFor = Exception.class)
    public void insertAAAAA() {
        Aopi aopi = new Aopi();
        aopi.setName("1");
        aopi.setAge(23);
        aopiRepositry.insert(aopi);
        try {
            insert();
        } catch (Exception e) {
            log.error("插入報錯", e);
        }
        // 判斷事務是否回滾
        if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {
            log.info("事務回滾了");
        } else {
            log.info("事務沒回滾");
        }
    }

2.6:場景3-1

    /**
     * 本類方法A 加了 @Transactional
     * 自己注入自己,再調用本類方法B,加了 @Transactional,異常被捕獲,並不拋出
     * 最終:回滾
     * <p>
     * 原因:aopiService bean 是一個代理bean,每次調用都會重新走代理調用邏輯。
     **/
    @Transactional(rollbackFor = Exception.class)
    public void insertAAAAAA() {
        Aopi aopi = new Aopi();
        aopi.setName("1");
        aopi.setAge(23);
        aopiRepositry.insert(aopi);
        try {
            aopiService.insert();
        } catch (Exception e) {
            log.error("插入報錯", e);
        }
        // 判斷事務是否回滾
        if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {
            log.info("事務回滾了");
        } else {
            log.info("事務沒回滾");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void insert() {
        int i = 1 / 0;
    }

3:結論

1:程序異常,事務是否回滾取決於 當前線程的事務狀態。

2:異常是否拋出並不是並不是一定會導致回滾失敗的原因。即使異常被捕獲,且不再次throw,事務也可能回滾。

3:拋出的異常不在rollback 范圍內,也不會進行回滾。


其他:

1:spring 用的cglib代理庫是自己的庫(依賴於spring-aop的包),並沒有引用第三方cglib庫。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM