Spring5——事務傳播機制原理解析

前言

Spring在TransactionDefinition接口中規定了7種類型的事務傳播行為,它們規定了事務方法和事務方法發生嵌套調用時事務如何進行傳播,即協調已經有事務標識的方法之間的發生調用時的事務上下文的規則(是否要有獨立的事務隔離級別和鎖)。

概述

當我們調用一個基于Spring的Service接口的事務方法(如UserService#addUser())時,它將運行于Spring管理的事務環境中,Service接口方法可能會在內部調用其它的Service接口方法以共同完成一個完整的業務操作,因此就會產生服務接口方法嵌套調用的情況, Spring通過事務傳播行為控制當前的事務如何傳播到被嵌套調用的目標服務接口方法中。

事務傳播是Spring進行事務管理的重要概念,其重要性怎么強調都不為過。但是事務傳播行為也是被誤解最多的地方,在本文里,我們將詳細分析不同事務傳播行為的表現形式,掌握它們之間的區別。

事務傳播行為種類

  • PROPAGATION_REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。
    配置方式:TransactionDefinition.PROPAGATION_REQUIRED

  • PROPAGATION_SUPPORTS:支持當前事務,如果當前沒有事務,就以非事務方式執行。
    配置方式:TransactionDefinition.PROPAGATION_SUPPORTS

  • PROPAGATION_MANDATORY:使用當前的事務,如果當前沒有事務,就拋出異常。
    配置方式:TransactionDefinition.PROPAGATION_MANDATORY

  • PROPAGATION_REQUIRES_NEW:新建事務,如果當前存在事務,把當前事務掛起。
    配置方式:TransactionDefinition.PROPAGATION_REQUIRES_NEW

  • PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
    配置方式:TransactionDefinition.PROPAGATION_NOT_SUPPORTED

  • PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則拋出異常。
    配置方式:TransactionDefinition.PROPAGATION_NEVER

  • PROPAGATION_NESTED:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。
    配置方式:TransactionDefinition.PROPAGATION_NESTED

首先我們要明確幾點基本知識:

  • 1、事務傳播機制只適用于不同bean之間方法的調用,如果一個bean中的兩個方法互相調用并不會使用到事務傳播。比如,一個bean的method1的事務傳播級別為Required,method2的事務傳播級別為Never,我們在method1里面調用method2。首先method1會開啟一個事務,而method2也沒有報錯并正確執行了,而且也在method1開啟的事務之中,說明事務傳播機制在一個bean自己的方法互相調用中并不起作用,只要一個方法開啟了事務,那這個在方法里調用當前bean的其他方法都在這個事務中運行,而不管其他方法的事務傳播機制是如何配置的。

  • 2、事務方法里如果拋RuntimeException,則會導致所有相關事務回滾,個別事務傳播機制有點特殊,我們下面會講到。

  • 3、事務方法里如果拋Throwable或者Exception,默認不會導致相關事務回滾,一般都會在出異常的地方提交,就有可能出現部分提交的問題。但可以配置rollback-for屬性來控制。

源碼解析:AbstractPlatformTransactionManager

  • 事務的傳播特性源碼主要位于AbstractPlatformTransactionManager類的handleExistingTransaction方法中。

PROPAGATION_NEVER

  • 該特性規定了以非事物方式執行,如果當前存在事物,則拋出異常。
private TransactionStatus handleExistingTransaction(
        TransactionDefinition definition, Object transaction, boolean debugEnabled)
        throws TransactionException {

    // 1.PROPAGATION_NEVER --> 以非事物方式執行,如果當前存在事物,則拋出異常。
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
        throw new IllegalTransactionStateException(
                "Existing transaction found for transaction marked with propagation 'never'");
    }

    ......
}

PROPAGATION_NOT_SUPPORTED

  • 該特性規定了以非事物方式執行,如果當前存在事物,則掛起當前事物。
private TransactionStatus handleExistingTransaction(
        TransactionDefinition definition, Object transaction, boolean debugEnabled)
        throws TransactionException {

    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
        if (debugEnabled) {
            logger.debug("Suspending current transaction");
        }
        // 重點:掛起已有事物
        Object suspendedResources = suspend(transaction);
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        // 創建新事物,注意:transaction參數為null,所以這里創建的不是一個真正的事物
        return prepareTransactionStatus(
                definition, null, false, newSynchronization, debugEnabled, suspendedResources);
    }

    ......
}

這里又涉及到一個概念,事務掛起(這個前面沒有介紹,因為前面都是單個事物)。下面分析一下事物掛起的流程:

@Nullable
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
    // 1.如果存在事物同步回調接口
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        // 1.1 掛起事務同步回調接口
        List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
        try {
            // 掛起事物
            Object suspendedResources = null;
            if (transaction != null) {
                suspendedResources = doSuspend(transaction);
            }
            // 獲取已有事物名稱
            String name = TransactionSynchronizationManager.getCurrentTransactionName();
            // 清空已有事物名稱
            TransactionSynchronizationManager.setCurrentTransactionName(null);
            // 獲取已有事物的readOnly屬性值
            boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
            // 將已有事物的readOnly屬性值設置為false
            TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
            // 獲取已有事物數據庫事物隔離級別
            Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
            // 清空已有事物數據庫事物隔離級別
            TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
            // 獲取已有事物激活標識
            boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
            // 將當前事物激活標識設置為false
            TransactionSynchronizationManager.setActualTransactionActive(false);
            //將上面獲取到的一系列事物屬性,重新封裝至SuspendedResourcesHolder對象,并返回
            return new SuspendedResourcesHolder(
                    suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
        }
        catch (RuntimeException | Error ex) {
            // doSuspend failed - original transaction is still active...
            doResumeSynchronization(suspendedSynchronizations);
            throw ex;
        }
    }
    // 不存在事物同步回調接口,且當前事物不為空
    else if (transaction != null) {
        // Transaction active but no synchronization active.
        // 事物已經被激活,但是沒有事物同步回調,則直接掛起當前事物即可
        Object suspendedResources = doSuspend(transaction);
        // 返回掛起的事物資源
        return new SuspendedResourcesHolder(suspendedResources);
    }
    // 處理沒有事物的情況...
    else {
        // Neither transaction nor synchronization active.
        return null;
    }
}

首先來看事物同步回調接口,該接口會在prepareSynchronization方法中設置,即使我們沒有自定義事物同步回調接口,Spring默認也會為當前事物創建一個空的事物同步回調接口。關于該接口的使用,可以參考上一節中的例子。(注意:事物同步回調接口是與當前線程綁定的)。下面看事物同步接口是如何掛起的:

private List<TransactionSynchronization> doSuspendSynchronization() {
    // 1.獲取當前線程的所有事物同步回調
    List<TransactionSynchronization> suspendedSynchronizations =
            TransactionSynchronizationManager.getSynchronizations();
    // 2.循環并掛起所有同步回調接口
    for (TransactionSynchronization synchronization : suspendedSynchronizations) {
        synchronization.suspend();
    }
    // 3.清除資源
    TransactionSynchronizationManager.clearSynchronization();
    return suspendedSynchronizations;
}

其次來看掛起事物的流程,注釋里已經寫了很清楚了,最后會將已有的事物屬性封裝到SuspendedResourcesHolder對象中,該類就持有了被掛起事物的屬性。接下來調用prepareTransactionStatus方法并將suspendedResources入參,這樣一來,新創建的事物就持有了被掛起事物的的屬性,就會形成一個事物鏈。而且新創建的事物transaction參數為null,所以PROPAGATION_NOT_SUPPORTED特性是不會真正開啟事物的。

// 創建新事物,注意:transaction參數為null,所以這里創建的不是一個真正的事物
return prepareTransactionStatus(
        definition, null, false, newSynchronization, debugEnabled, suspendedResources);

PROPAGATION_REQUIRES_NEW

  • 該特性下會掛起已有事物并新建一個事物。
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 {
            return startTransaction(definition, transaction, debugEnabled, suspendedResources);
        }
        catch (RuntimeException | Error beginEx) {
            resumeAfterBeginException(transaction, suspendedResources, beginEx);
            throw beginEx;
        }
    }

    ......
}


private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
        boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {

    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    // 創建事物
    DefaultTransactionStatus status = newTransactionStatus(
            definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    // 開啟事物
    doBegin(transaction, definition);
    // 初始化事物同步屬性
    prepareSynchronization(status, definition);
    return status;
}

PROPAGATION_NESTED

private TransactionStatus handleExistingTransaction(
        TransactionDefinition definition, Object transaction, boolean debugEnabled)
        throws TransactionException {

    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        // 如果不允許嵌套事物,則拋出異常
        if (!isNestedTransactionAllowed()) {
            throw new NestedTransactionNotSupportedException(
                    "Transaction manager does not allow nested transactions by default - " +
                    "specify 'nestedTransactionAllowed' property with value 'true'");
        }
        if (debugEnabled) {
            logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
        }
        //下面對JtaTransactionManager和AbstractPlatformTransactionManager分別進行處理
        //useSavepointForNestedTransaction(),是否為嵌套事務使用保存點
        // 1.對于JtaTransactionManager-->返回false
        // 2.對于AbstractPlatformTransactionManager-->返回true
        if (useSavepointForNestedTransaction()) {
            // Create savepoint within existing Spring-managed transaction,
            // through the SavepointManager API implemented by TransactionStatus.
            // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
            // 創建保存點在現有spring管理事務,通過TransactionStatus SavepointManager API實現。
            // 通常使用JDBC 3.0保存點。永遠不要激活Spring同步。
            DefaultTransactionStatus status =
                    prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
            // 創建保存點
            status.createAndHoldSavepoint();
            return status;
        }
        else {
            // Nested transaction through nested begin and commit/rollback calls.
            // Usually only for JTA: Spring synchronization might get activated here
            // in case of a pre-existing JTA transaction.
            // 通過嵌套的開始,提交調用,及回滾調用進行嵌套事務。
            // 只對JTA有效,如果已經存在JTA事務,這里可能會激活Spring同步。
            return startTransaction(definition, transaction, debugEnabled, null);
        }
    }

    ......
}


private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
        boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {

    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    // 創建事物
    DefaultTransactionStatus status = newTransactionStatus(
            definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    // 開啟事物
    doBegin(transaction, definition);
    // 初始化事物同步屬性
    prepareSynchronization(status, definition);
    return status;
}

這里對AbstractPlatformTransactionManager和JtaTransactionManager分別做了不同的處理,重點看前者。AbstractPlatformTransactionManager是允許使用保存點的。接下來看保存點的創建過程:

public abstract class AbstractTransactionStatus implements TransactionStatus {

    // 創建保存點
    public void createAndHoldSavepoint() throws TransactionException {
        setSavepoint(getSavepointManager().createSavepoint());
    }
}


public class DefaultTransactionStatus extends AbstractTransactionStatus {

    // 獲取SavepointManager
    @Override
    protected SavepointManager getSavepointManager() {
        Object transaction = this.transaction;
        if (!(transaction instanceof SavepointManager)) {
            throw new NestedTransactionNotSupportedException(
                    "Transaction object [" + this.transaction + "] does not support savepoints");
        }
        // SavepointManager就是當前事物
        return (SavepointManager) transaction;
    }
}


public abstract class JdbcTransactionObjectSupport implements SavepointManager, SmartTransactionObject {

    // 創建保存點
    @Override
    public Object createSavepoint() throws TransactionException {
        // 獲取ConnectionHolder
        ConnectionHolder conHolder = getConnectionHolderForSavepoint();
        try {
            // 如果當前連接不支持保存點,拋出異常
            if (!conHolder.supportsSavepoints()) {
                throw new NestedTransactionNotSupportedException(
                        "Cannot create a nested transaction because savepoints are not supported by your JDBC driver");
            }
            // 如果當前連接的RollbackOnly已經被標記為true,拋出異常
            if (conHolder.isRollbackOnly()) {
                throw new CannotCreateTransactionException(
                        "Cannot create savepoint for transaction which is already marked as rollback-only");
            }
            // 創建保存點
            return conHolder.createSavepoint();
        }
        catch (SQLException ex) {
            throw new CannotCreateTransactionException("Could not create JDBC savepoint", ex);
        }
    }
}

總體來講創建保存點的過程比較簡單,最終會調用數據庫驅動的底層方法去創建保存點,感興趣的可以自己跟蹤查看。創建完保存點之后將結果設置到TransactionStatus對象中。
該特性下會將新事物在原有事物內,以嵌套的方式運行,該特性也不會開啟一個新的事物。

PROPAGATION_SUPPORTS和PROPAGATION_REQUIRED

private TransactionStatus handleExistingTransaction(
        TransactionDefinition definition, Object transaction, boolean debugEnabled)
        throws TransactionException {

    // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
    if (debugEnabled) {
        logger.debug("Participating in existing transaction");
    }
    if (isValidateExistingTransaction()) {
        // 驗證事物隔離級別
        // 如果當前事物的隔離級別不為默認隔離級別,則比較當前事物隔離級別與已有事物隔離級別,
        // 如不同,則拋出事物隔離級別不兼容異常
        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
            Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
            if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
                Constants isoConstants = DefaultTransactionDefinition.constants;
                throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] specifies isolation level which is incompatible with existing transaction: " +
                        (currentIsolationLevel != null ?
                                isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
                                "(unknown)"));
            }
        }
        
        // 驗證事物只讀屬性
        // 如果當前事物可寫,但是已有的事物是只讀,則拋出異常
        if (!definition.isReadOnly()) {
            if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] is not marked as read-only but existing transaction is");
            }
        }
    }
    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

isValidateExistingTransaction()該函數的作用是指定新事物參與已有事物時,新舊兩個事物的驗證級別。該屬性值默認為false,寬松范圍的驗證,也就是不驗證。如果將該屬性值改為true的話,那么將會驗證新舊兩個事物的數據庫事物隔離級別、事物只讀屬性是否相同。對于PROPAGATION_MANDATORY,從Spring的注釋上沒有看到對改屬性的處理,但是該屬性也會走這個分支,即使用已有事物,由于到這里肯定是已經存在事物的,該特性在這里不會拋出異常。
該特性也不會開啟一個新的事物,而是繼續在原有事物中運行或者以非事物方式運行

參考:
https://www.cnblogs.com/softidea/p/5962612.html

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。