Spring : Spring @Transactional-事物回滾

1.美圖

在這裏插入圖片描述

2.概述

創建事務參考:

3.前文回顧

// 2.處理聲明式事物
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
    // Standard transaction demarcation with getTransaction and commit/rollback calls.
    // 2.2 創建事物
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    System.out.println("==創建了名爲:["+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.
        // 2.3 繼續調用方法攔截器鏈,這裏一般將會調用目標類的方法,如:com.lyc.cn.v2.day09.AccountServiceImpl.save方法
        retVal = invocation.proceedWithInvocation();
    } catch (Throwable ex) {
        // target invocation exception
        // 2.4 如果目標類方法拋出異常,則在此處理,例如:事物回滾
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
    } finally {
        // 2.5 清除上一步創建的事物信息
        cleanupTransactionInfo(txInfo);
    }
    // 2.6 調用成功完成後執行,但不是在異常被處理後執行。如果我們不創建事務,就什麼也不做。
    commitTransactionAfterReturning(txInfo);
    return retVal;
}

關於攔截器鏈的調用、目標方法的調用等前面都有介紹。

接下來的處理會分爲兩種情況(假如當前一定存在事物):

  1. 有異常,事物即有可能會被回滾、也有可能會被提交;
  2. 無異常,事物會被提交,

接下來對兩種情況逐個分析。因爲catch方法的代碼比較靠前,先分析有異常的情況。細心的同學可能會發現,無論是否拋出異常都會調用completeTransactionAfterThrowing方法去完成事物,只是該方法被重載了,傳遞的參數不同而已,當然其內部處理細節肯定是不同的。

4.回滾判斷

/**
	 * 處理異常以完成事物,基於配置該事物可能回滾也可能提交
	 *
	 * Handle a throwable, completing the transaction.
	 * We may commit or roll back, depending on the configuration.
	 * @param txInfo information about the current transaction
	 * @param ex throwable encountered
	 */
	protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
						"] after exception: " + ex);
			}
			// 1.回滾
			/**
			 * txInfo.transactionAttribute.rollbackOn(ex)判斷回滾的條件:
			 *
			 * 1. 如果自定了RollbackRuleAttribute列表,如果當前異常匹配到了RollbackRuleAttribute其中的條目,則回滾
			 *    例如:可以通過rollbackFor指定觸發回滾的異常@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
			 *
			 * 2. 否則如果異常是RuntimeException或者Error的類型,則回滾
			 *
			 * 3. 其他的異常是不會回滾的,這裏要注意一下...
			 */
			if (txInfo.transactionAttribute != null && 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;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					throw ex2;
				}
			}
			// 2.如果未能滿足回滾條件,則有可能會提交事物,也有可能會回滾事物
			// 注意:如果TransactionStatus.isRollbackOnly()爲true,則仍然會執行回滾
			else {
				// We don't roll back on this exception.
				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
				try {
					txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					throw ex2;
				}
			}
		}
	}

txInfo.transactionAttribute.rollbackOn(ex)這段代碼是對當前異常是否滿足回滾條件的判斷。Spring事物中默認只有RuntimeExceptionError兩種類型的被捕獲之後,事物纔會回滾。因爲在DefaultTransactionAttribute類中有:

public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}

但是在實際應用當中,可能會有很多自定義異常,如果想要自定義異常發生時也回滾事物,怎麼辦呢?這時候就要用到rollbackFor屬性,例如:@Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class),Spring會將我們自定義的異常註冊到RuleBasedTransactionAttribute類的RollbackRuleAttribute屬性集合中。總結如下

  1. 匹配到自定義異常,回滾。
  2. 如果異常是RuntimeException或者Error的類型,回滾。
  3. 其他異常,不回滾。

completeTransactionAfterThrowing方法的else分支中通過commit方法對當前事物進行了提交操作,但是,commit方法並不一定會真正的提交事物,也有可能會回滾事物,這一點留在事物提交分析中講解,接下來看事物是如何回滾的。

5.回滾

/**
	 * Process an actual rollback.
	 * The completed flag has already been checked.
	 * @param status object representing the transaction
	 * @throws TransactionException in case of rollback failure
	 *
	 * 執行回滾
	 */
	private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
		try {
			boolean unexpectedRollback = unexpected;

			try {
				// 1.事物回滾前調用事物同步接口
				triggerBeforeCompletion(status);

				// 2.如果有保存點,則回滾到保存點
				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Rolling back transaction to savepoint");
					}
					status.rollbackToHeldSavepoint();
				}
				// 3.如果當前事物是一個新的事物,則調用doRollback執行給定事物的回滾
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction rollback");
					}
					doRollback(status);
				}
				else {
					// Participating in larger transaction
					// 4.如果當前事物並非獨立事物,則將當前事物的rollbackOnly屬性標記爲true,等到事物鏈完成之後,一起執行回滾

					// 如果當前存在事物,但是事物的rollbackOnly屬性已經被標記爲true
					// 或者globalRollbackOnParticipationFailure(返回是否僅在參與事務失敗後纔將現有事務全局標記爲回滾)爲true
					if (status.hasTransaction()) {
						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
							}
							// 則將ConnectionHolder中的rollbackOnly標記爲true
							doSetRollbackOnly(status);
						}
						// 5.如果當前不存在事物,則不執行任何操作
						else {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
							}
						}
					}
					else {
						logger.debug("Should roll back transaction but cannot - no transaction available");
					}
					// Unexpected rollback only matters here if we're asked to fail early
					if (!isFailEarlyOnGlobalRollbackOnly()) {
						unexpectedRollback = false;
					}
				}
			}
			catch (RuntimeException | Error ex) {
				// 6.事物回滾後調用事物同步接口
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
				throw ex;
			}

			triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

			// Raise UnexpectedRollbackException if we had a global rollback-only marker
			if (unexpectedRollback) {
				throw new UnexpectedRollbackException(
						"Transaction rolled back because it has been marked as rollback-only");
			}
		}
		finally {
			// 7.事物完成後清理資源
			cleanupAfterCompletion(status);
		}
	}

說明:第2、4點涉及到了嵌套事物回滾,請參考Spring : Spring @Transactional-嵌套事物回滾

  1. 事物完成前、完成後觸發器的調用,例如:我們在上一節的業務方法裏註冊了TransactionSynchronizationAdapter接口,那麼在這裏會分別執行我們實現了接口的方法,我們可以通過該接口來做一些額外的功能擴展。
  2. 如果有保存點,則調用rollbackToHeldSavepoint回滾到保存點(後面嵌套事物章節分析)
  3. 如果當前事物是一個新的事物,則調用doRollback執行給定事物的回滾
  4. 如果當前事物並非獨立事物,則將當前事物的rollbackOnly屬性標記爲true,等到事物鏈完成之後,一起執行回滾,大概的流程就是這樣了,因爲我們分析的是單service下的單個業務方法調用,所以這裏我們還是隻分析最簡單的doRollback正常回滾調用(後面嵌套事物章節分析)

不同的事物管理器對doRollback回滾有不同的實現,這裏我們只看DataSourceTransactionManager的回滾方法:

@Override
protected void doRollback(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    Connection con = txObject.getConnectionHolder().getConnection();
    if (status.isDebug()) {
        logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
    }
    try {
        con.rollback();
    }
    catch (SQLException ex) {
        throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
    }
}

從ConnectionHolder中拿到連接並執行回滾。這裏會涉及到一些數據庫底層的東西了,感興趣的同學可以自己深入研究。

到這裏事物的回滾操作就完成了,但是不要忘了,這裏一定要釋放掉事物相關的資源。以免造成數據庫連接泄露、內存泄露等錯誤。

/**
	 * Clean up after completion, clearing synchronization if necessary,
	 * and invoking doCleanupAfterCompletion.
	 * @param status object representing the transaction
	 * @see #doCleanupAfterCompletion
	 */
	private void cleanupAfterCompletion(DefaultTransactionStatus status) {
		// 1.將當前事物狀態標記爲已完成
		status.setCompleted();
		// 2.清除synchronization
		if (status.isNewSynchronization()) {
			TransactionSynchronizationManager.clear();
		}
		// 3.事務完成後清理資源。
		if (status.isNewTransaction()) {
			doCleanupAfterCompletion(status.getTransaction());
		}
		// 4.從嵌套事物中恢復被掛起的資源
		if (status.getSuspendedResources() != null) {
			if (status.isDebug()) {
				logger.debug("Resuming suspended transaction after completion of inner transaction");
			}
			Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
			resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
		}
	}

第1、2很簡單,第4又涉及到了事物嵌套,我們只有分析一下第3步了:

@Override
	protected void doCleanupAfterCompletion(Object transaction) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;

		// Remove the connection holder from the thread, if exposed.
		// 解綁ConnectionHolder
		if (txObject.isNewConnectionHolder()) {
			TransactionSynchronizationManager.unbindResource(obtainDataSource());
		}

		// Reset connection.
		// 重置連接
		Connection con = txObject.getConnectionHolder().getConnection();
		try {
			if (txObject.isMustRestoreAutoCommit()) {
				con.setAutoCommit(true);
			}
			DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
		}
		catch (Throwable ex) {
			logger.debug("Could not reset JDBC Connection after transaction", ex);
		}

		if (txObject.isNewConnectionHolder()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
			}
			DataSourceUtils.releaseConnection(con, this.dataSource);
		}

		txObject.getConnectionHolder().clear();
	}

等到事物資源清理完成之後,事物就徹底的結束了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章