天天看點

淺析spring聲明式事務使用

關于spring事務主要有四種特性和五種隔離級别和7種傳播行為,這篇文章就來好好總結一下。

1. 四種特性

  • 原子性 (atomicity):簡單了解事物是可能是多個活動的工作單元要麼全部發生要麼全部不發生。
  • 一緻性 (consistency):事務的執行的前後資料的完整性保持一緻.
  • 隔離性 (isolation):一個事務執行的過程中,不應該受到其他事務的幹擾。
  • 持久性(durability) : 一旦事務完成,結果應當儲存起來,在系統崩潰後可以恢複回來。

2. 一般配置

  • spring的xml中配置如下:
淺析spring聲明式事務使用

是在事務管理器中配置的,關于事務管理器有不明白的可以翻下之前的文章。

springboot中和注解形式的是在@Transactional注解中配置的(添加注解時添加這些):

淺析spring聲明式事務使用

除了添加@Transactional注解,還可以使用transactionManager手動處理:

淺析spring聲明式事務使用

3. spring事務隔離級别設定(對應資料庫級别的隔離級别)

  • ISOLATION_DEFAULT 使用底層預設,與資料庫設定的隔離級别保持一緻
  • ISOLATIONREADUNCOMMITTED 允許事物讀取其他并行事物還沒送出的資料,會發生髒讀、幻讀和不可重複讀。
  • ISOLATIONREADCOMMITTED 保證一個事物送出後才能被另外一個事務讀取。另外一個事務不能讀取該事物未送出的資料。在read_uncommitted的基礎上可以防止髒讀。
  • ISOLATIONREPEATABLEREAD 除了保證一個事務不能被另外一個事務讀取未送出的資料之外,還能避免不可重複讀。可以防止髒讀和不可重複讀。
  • ISOLATION_SERIALIZABLE 串行化會讓事務順序執行。能避免髒讀、幻讀和不可重複讀。

關于髒讀、幻讀和不可重複讀:

1.髒讀: 一個事務讀到了另一個事務未送出的資料。2.不可重複讀: 一個事務讀到了另一個事務已經送出的update的資料,導緻多次查詢結果不一緻。當一個事務中有兩次相同的查詢操作時,第一個查詢得到結果後,在第二個查詢執行時這條結果被修改了,會導緻在同一個事務中兩個查詢的結果不一緻。3.幻讀: 一個事務中讀到另一個事務已經送出的插入資料導緻多次查詢結果不一緻。當一個事務中有兩次或多次相同的查詢操作時,第一個查詢得到的不存在的記錄,第二個查詢中卻發現被另一個事務插入了,這就是幻讀。

關于隔離級别,在spring中的設定: 在org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin方法中:

淺析spring聲明式事務使用

對應的org.springframework.jdbc.datasource.DataSourceUtils#prepareConnectionForTransaction方法:

淺析spring聲明式事務使用

4. spring事務的傳播行為

業務方法在容器中運作 存在父事務(即事務嵌套) 不存在父事務
REQUIRED 在父事務中運作 自己建立一個事務
SUPPORTS 在父事務中運作 正常執行
MANDATORY 在父事務中運作 抛出異常
REQUIRES_NEW 建立事務,将父事務挂起(suspend) 自己建立事務
NOT_SUPPORTED 如果存在方法調用将父事務挂起,調用結束恢複 不會開啟事務
NEVER 抛出 異常 正常執行
NESTED 它的事務和父事務是相依的,與父事務一起送出,一起復原 自己建立

上面存在父事務的場景主要是sevice調用service或dao層調用dao層的情況。

下面我們看看在spring源碼裡面的對應設定: 在執行時确定是否需要建立事務會調用擷取事務的方法org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction:

/**
     * This implementation handles propagation behavior. Delegates to
     * {@code doGetTransaction}, {@code isExistingTransaction}
     * and {@code doBegin}.
     * @see #doGetTransaction
     * @see #isExistingTransaction
     * @see #doBegin
     */
    @Override
    public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
        Object transaction = doGetTransaction();

        // Cache debug flag to avoid repeated checks.
        boolean debugEnabled = logger.isDebugEnabled();

        if (definition == null) {
            // Use defaults if no transaction definition given.
            definition = new DefaultTransactionDefinition();
        }

        if (isExistingTransaction(transaction)) {//如果目前存在事務
            // Existing transaction found -> check propagation behavior to find out how to behave.
            //進入處理已經存在事務的處理方法
            return handleExistingTransaction(definition, transaction, debugEnabled);
        }
        //----------------------下面都是目前不存在事務的處理邏輯---------------------------
        // Check definition settings for new transaction.
        if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
        }

        // No existing transaction found -> check propagation behavior to find out how to proceed.
        //當傳播行為是PROPAGATION_MANDATORY時,如果沒有父事務,則抛出異常
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException(
                    "No existing transaction found for transaction marked with propagation 'mandatory'");
        }
        else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            SuspendedResourcesHolder suspendedResources = suspend(null);
            if (debugEnabled) {
                logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
            }
            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 ex) {
                resume(null, suspendedResources);
                throw ex;
            }
            catch (Error err) {
                resume(null, suspendedResources);
                throw err;
            }
        }
        else {
            // Create "empty" transaction: no actual transaction, but potentially synchronization.
            if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
                logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                        "isolation level will effectively be ignored: " + definition);
            }
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
        }
    }           

複制

  • 上面的方法主要有兩點,是否存在父事務。
  • 如果存在父事務,則進入handleExistingTransaction方法,會按照上面表格上的存在父事務的情況各種傳播行為下的事務邏輯進行處理。
  • 如果不存在父事務則進入下面的邏輯,針對TransactionDefinition.PROPAGATIONREQUIRED、TransactionDefinition.PROPAGATIONREQUIRESNEW、TransactionDefinition.PROPAGATIONNESTED這三種在父事務不存在時會建立事務,最終會進入newTransactionStatus方法。
  • PROPAGATIONREQUIRESNEW 啟動一個新的, 不依賴于環境的 “内部” 事務. 這個事務将被完全 commited 或 rolled back 而不依賴于外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等. 當内部事務開始執行時, 外部事務将被挂起, 内務事務結束時, 外部事務将繼續執行。
  • PROPAGATION_NESTED 開始一個 “嵌套的” 事務, 它是已經存在事務的一個真正的子事務. 潛套事務開始執行時, 它将取得一個 savepoint. 如果這個嵌套事務失敗, 我們将復原到此 savepoint. 潛套事務是外部事務的一部分, 隻有外部事務結束後它才會被送出。