這一次搞懂Spring事務是如何傳播的

前言

上一篇分析了事務註解的解析過程,本質上是將事務封裝爲切面加入到AOP的執行鏈中,因此會調用到MethodInceptor的實現類的invoke方法,而事務切面的Interceptor就是TransactionInterceptor,所以本篇直接從該類開始。

正文

事務切面的調用過程

	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// Adapt to TransactionAspectSupport's invokeWithinTransaction...
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}

這個方法本身沒做什麼事,主要是調用了父類的invokeWithinTransaction方法,注意最後一個參數,傳入的是一個lambda表達式,而這個表達式中的調用的方法應該不陌生,在分析AOP調用鏈時,就是通過這個方法傳遞到下一個切面或是調用被代理實例的方法,忘記了的可以回去看看。

	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {

		// If the transaction attribute is null, the method is non-transactional.
		//獲取事務屬性類 AnnotationTransactionAttributeSource
		TransactionAttributeSource tas = getTransactionAttributeSource();

		//獲取方法上面有@Transactional註解的屬性
		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)) {
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
			Object retVal = null;
			try {
				// 調用proceed方法
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// target invocation exception
				//事務回滾
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}
			//事務提交
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}
		
	// 省略了else
	}

這個方法邏輯很清晰,一目瞭然,if裏面就是對聲明式事務的處理,先調用createTransactionIfNecessary方法開啓事務,然後通過invocation.proceedWithInvocation調用下一個切面,如果沒有其它切面了,就是調用被代理類的方法,出現異常就回滾,否則提交事務,這就是Spring事務切面的執行過程。但是,我們主要要搞懂的就是在這些方法中是如何管理事務以及事務在多個方法之間是如何傳播的。

事務的傳播性概念

傳播性是Spring自己搞出來的,數據庫是沒有的,因爲涉及到方法間的調用,那麼必然就需要考慮事務在這些方法之間如何流轉,所以Spring提供了7個傳播屬性供選擇,可以將其看成兩大類,即是否支持當前事務:

  1. 支持當前事務(在同一個事務中):
  • PROPAGATION_REQUIRED:支持當前事務,如果不存在,就新建一個事務。
  • PROPAGATION_MANDATORY:支持當前事務,如果不存在,就拋出異常。
  • PROPAGATION_SUPPORTS:支持當前事務,如果不存在,就不使用事務。
  1. 不支持當前事務(不在同一個事務中):
  • PROPAGATION_NEVER:以非事務的方式運行,如果有事務,則拋出異常。
  • PROPAGATION_NOT_SUPPORTED:以非事務的方式運行,如果有事務,則掛起當前事務。
  • PROPAGATION_REQUIRES_NEW:新建事務,如果有事務,掛起當前事務(兩個事務相互獨立,父事務回滾不影響子事務)。
  • PROPAGATION_NESTED:如果當前事務存在,則嵌套事務執行(指必須依存父事務,子事務不能單獨提交且父事務回滾則子事務也必須回滾,而子事務若回滾,父事務可以回滾也可以捕獲異常)。如果當前沒有事務,則進行與PROPAGATION_REQUIRED類似的操作。

別看屬性這麼多,實際上我們主要用的是PROPAGATION_REQUIRED默認屬性,一些特殊業務下可能會用到PROPAGATION_REQUIRES_NEW以及PROPAGATION_NESTED。下面我會假設一個場景,並主要分析這三個屬性。

public class A {
	
	@Autowired
	private B b;

	@Transactional
	public void addA() {
		b.addB();
	}
}

public class B {
	@Transactional
	public void addB() {
		// doSomething...
	}
}

上面我創建了A、B兩個類,每個類中有一個事務方法,使用了聲明式事務並採用的默認傳播屬性,在A中調用了B的方法。
當請求來了調用addA時,首先調用的是代理對象的方法,因此會進入createTransactionIfNecessary方法開啓事務:

	protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

		// If no name specified, apply method identification as transaction name.
		if (txAttr != null && txAttr.getName() == null) {
			txAttr = new DelegatingTransactionAttribute(txAttr) {
				@Override
				public String getName() {
					return joinpointIdentification;
				}
			};
		}

		TransactionStatus status = null;
		if (txAttr != null) {
			if (tm != null) {
				//開啓事務,這裏重點看
				status = tm.getTransaction(txAttr);
			}
			else {
			}
		}
		//創建事務信息對象,記錄新老事務信息對象
		return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
	}

實際上開啓事務是通過AbstractPlatformTransactionManager做的,而這個類是一個抽象類,具體實例化的對象就是我們在項目裏常配置的DataSourceTransactionManager對象。

	public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
		//這裏重點看,.DataSourceTransactionObject拿到對象
		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();
		}

		//第一次進來connectionHolder爲空的,所以不存在事務
		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.
		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);

					//開啓事務,重點看看 DataSourceTransactionObject
					doBegin(transaction, definition);

					//開啓事務後,改變事務狀態
					prepareSynchronization(status, definition);
					return status;
			}
			catch (RuntimeException | Error ex) {
				resume(null, suspendedResources);
				throw ex;
			}
		}
		else {
			boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
			return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
		}
	}

這個方法流程比較長,一步步來看,先調用doGetTransaction方法獲取一個DataSourceTransactionObject對象,這個類是JdbcTransactionObjectSupport的子類,在父類中持有了一個ConnectionHolder對象,見名知意,這個對象保存了當前的連接。

	protected Object doGetTransaction() {
		//管理connection對象,創建回滾點,按照回滾點回滾,釋放回滾點
		DataSourceTransactionObject txObject = new DataSourceTransactionObject();

		//DataSourceTransactionManager默認是允許嵌套事務的
		txObject.setSavepointAllowed(isNestedTransactionAllowed());

		//obtainDataSource() 獲取數據源對象,其實就是數據庫連接塊對象
		ConnectionHolder conHolder =
				(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
		txObject.setConnectionHolder(conHolder, false);
		return txObject;
	}

追溯getResource方法可以看到ConnectionHolder 是從ThreadLocal裏獲取的,也就是當前線程,key是DataSource對象;但是仔細思考下我們是第一次進來,所以這裏肯定獲取不到的,反之,要從這裏獲取到值,那必然是同一個線程第二次及以後進入到這裏,也就是在addA調用addB時,另外需要注意這裏保存ConnectionHolderDataSourceTransactionObject對象時是將newConnectionHolder屬性設置爲false了的。
繼續往後,創建完transaction對象後,會調用isExistingTransaction判斷是否已經存在一個事務,如果存在就會調用handleExistingTransaction方法,這個方法就是處理事務傳播的核心方法,因爲我們是第一次進來,肯定不存在事務,所以先跳過。
再往後,可以看到就是處理不同的傳播屬性,主要看到下面這個部分:

		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);

					//開啓事務,重點看看 DataSourceTransactionObject
					doBegin(transaction, definition);

					//開啓事務後,改變事務狀態
					prepareSynchronization(status, definition);
					return status;
			}
			catch (RuntimeException | Error ex) {
				resume(null, suspendedResources);
				throw ex;
			}
		}

第一次進來時,PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEWPROPAGATION_NESTED都會進入到這裏,首先會調用suspend掛起當前存在的事務,在這裏沒啥作用。接下來通過newTransactionStatus創建了DefaultTransactionStatus對象,這個對象主要就是存儲當前事務的一些狀態信息,需要特別注意newTransaction屬性設置爲了true,表示是一個新事務。狀態對象創建好之後就是通過doBegin開啓事務,這是一個模板方法

	protected void doBegin(Object transaction, TransactionDefinition definition) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		Connection con = null;

		try {
			//如果沒有數據庫連接
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
				//從連接池裏面獲取連接
				Connection newCon = obtainDataSource().getConnection();
				if (logger.isDebugEnabled()) {
					logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
				}
				//把連接包裝成ConnectionHolder,然後設置到事務對象中
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			}

			txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
			con = txObject.getConnectionHolder().getConnection();

			//從數據庫連接中獲取隔離級別
			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
			txObject.setPreviousIsolationLevel(previousIsolationLevel);

			// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
			// so we don't want to do it unnecessarily (for example if we've explicitly
			// configured the connection pool to set it already).
			if (con.getAutoCommit()) {
				txObject.setMustRestoreAutoCommit(true);
				if (logger.isDebugEnabled()) {
					logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
				}
				//關閉連接的自動提交,其實這步就是開啓了事務
				con.setAutoCommit(false);
			}

			//設置只讀事務 從這一點設置的時間點開始(時間點a)到這個事務結束的過程中,其他事務所提交的數據,該事務將看不見!
			//設置只讀事務就是告訴數據庫,我這個事務內沒有新增,修改,刪除操作只有查詢操作,不需要數據庫鎖等操作,減少數據庫壓力
			prepareTransactionalConnection(con, definition);

			//自動提交關閉了,就說明已經開啓事務了,事務是活動的
			txObject.getConnectionHolder().setTransactionActive(true);

			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			}

			// Bind the connection holder to the thread.
			if (txObject.isNewConnectionHolder()) {
				//如果是新創建的事務,則建立當前線程和數據庫連接的關係
				TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
			}
		}

		catch (Throwable ex) {
			if (txObject.isNewConnectionHolder()) {
				DataSourceUtils.releaseConnection(con, obtainDataSource());
				txObject.setConnectionHolder(null, false);
			}
			throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
		}
	}

這個方法裏面主要做了六件事:

  • 首先從連接池獲取連接並保存到DataSourceTransactionObject對象中。
  • 關閉數據庫的自動提交,也就是開啓事務。
  • 獲取數據庫的隔離級別。
  • 根據屬性設置該事務是否爲只讀事務
  • 將該事務標識爲活動事務(transactionActive=true)。
  • ConnectionHolder對象與當前線程綁定。

完成之後通過prepareSynchronization將事務的屬性狀態設置到TransactionSynchronizationManager對象中進行管理。最後返回到createTransactionIfNecessary方法中創建TransactionInfo對象與當前線程綁定並返回。
通過以上的步驟就開啓了事務,接下來就是通過proceedWithInvocation調用其它切面,這裏我們先假設沒有其它切面了,那麼就是直接調用到A類的addA方法,在這個方法中又調用了B類的addB方法,那麼肯定也是調用到代理類的方法,因此又會進入到createTransactionIfNecessary方法中。但這次進來通過isExistingTransaction判斷是存在事務的,因此會進入到handleExistingTransaction方法:

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

		//不允許有事務,直接異常
		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
			throw new IllegalTransactionStateException(
					"Existing transaction found for transaction marked with propagation 'never'");
		}

		//以非事務方式執行操作,如果當前存在事務,就把當前事務掛起
		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
			if (debugEnabled) {
				logger.debug("Suspending current transaction");
			}
			//掛起當前事務
			Object suspendedResources = suspend(transaction);
			boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
			//修改事務狀態信息,把事務的一些信息存儲到當前線程中,ThreadLocal中
			return prepareTransactionStatus(
					definition, null, false, newSynchronization, debugEnabled, suspendedResources);
		}

		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 | Error beginEx) {
				resumeAfterBeginException(transaction, suspendedResources, beginEx);
				throw beginEx;
			}
		}

		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() + "]");
			}
			//默認是可以嵌套事務的
			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.
				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.
				boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
				DefaultTransactionStatus status = newTransactionStatus(
						definition, transaction, true, newSynchronization, debugEnabled, null);
				doBegin(transaction, definition);
				prepareSynchronization(status, definition);
				return status;
			}
		}
	
		// 省略
		
		boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
		return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
	}

這裏面也是對每個傳播屬性的判斷,先看PROPAGATION_REQUIRES_NEW的處理,因爲該屬性要求每次調用都開啓一個新的事務,所以首先會將當前事務掛起,怎麼掛起呢?

	protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
		if (TransactionSynchronizationManager.isSynchronizationActive()) {
			List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
			try {
				Object suspendedResources = null;
				//第一次進來,肯定爲null的
				if (transaction != null) {
					//吧connectionHolder設置爲空
					suspendedResources = doSuspend(transaction);
				}

				//做數據還原操作
				String name = TransactionSynchronizationManager.getCurrentTransactionName();
				TransactionSynchronizationManager.setCurrentTransactionName(null);
				boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
				TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
				Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
				TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
				boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
				TransactionSynchronizationManager.setActualTransactionActive(false);
				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;
		}
	}

	protected Object doSuspend(Object transaction) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		txObject.setConnectionHolder(null);
		//解除綁定關係,
		return TransactionSynchronizationManager.unbindResource(obtainDataSource());
	}

這裏明顯是進入第一個if並且會調用到doSuspend方法,整體來說掛起事務很簡單:首先將DataSourceTransactionObjectConnectionHolder設置爲空並解除與當前線程的綁定,之後將解除綁定的ConnectionHolder其它屬性(事務名稱、隔離級別、只讀屬性)通通封裝到SuspendedResourcesHolder對象,並將當前事務的活動狀態設置爲false。掛起事務之後又通過newTransactionStatus創建了一個新的事務狀態並調用doBegin開啓事務,這裏不再重複分析。
接着來看PROPAGATION_NESTED

		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() + "]");
			}
			//默認是可以嵌套事務的
			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.
				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.
				boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
				DefaultTransactionStatus status = newTransactionStatus(
						definition, transaction, true, newSynchronization, debugEnabled, null);
				doBegin(transaction, definition);
				prepareSynchronization(status, definition);
				return status;
			}
		}

這裏面可以看到如果允許嵌套事務,就會創建一個DefaultTransactionStatus對象(注意newTransaction是false,表明不是一個新事務)和回滾點;如果不允許嵌套,就會創建新事務並開啓。
當上面的判斷都不滿足時,也就是傳播屬性爲默認PROPAGATION_REQUIRED時,則只是創建了一個newTransaction爲false的DefaultTransactionStatus返回。
完成之後又是調用proceedWithInvocation,那麼就是執行B類的addB方法,假如沒有發生異常,那麼就會回到切面調用commitTransactionAfterReturning提交addB的事務:

	protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
		}
	}

	public final void commit(TransactionStatus status) throws TransactionException {

		processCommit(defStatus);
	}

	private void processCommit(DefaultTransactionStatus status) throws TransactionException {
		try {
			boolean beforeCompletionInvoked = false;

			try {
				boolean unexpectedRollback = false;
				prepareForCommit(status);
				triggerBeforeCommit(status);
				triggerBeforeCompletion(status);
				beforeCompletionInvoked = true;

				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Releasing transaction savepoint");
					}
					// 如果是nested,沒有提交,只是將savepoint清除掉了
					unexpectedRollback = status.isGlobalRollbackOnly();
					status.releaseHeldSavepoint();
				}
				//如果都是PROPAGATION_REQUIRED,最外層的纔會走進來統一提交,如果是PROPAGATION_REQUIRES_NEW,每一個事務都會進來
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction commit");
					}
					unexpectedRollback = status.isGlobalRollbackOnly();
					doCommit(status);
				}
				else if (isFailEarlyOnGlobalRollbackOnly()) {
					unexpectedRollback = status.isGlobalRollbackOnly();
				}
			}
		}
		finally {
			cleanupAfterCompletion(status);
		}
	}

主要邏輯在processCommit方法中。如果存在回滾點,可以看到並沒有提交事務,只是將當前事務的回滾點清除了;而如果是新事務,就會調用doCommit提交事務,也就是隻有PROPAGATION_REQUIRED屬性下的最外層事務PROPAGATION_REQUIRES_NEW屬性下的事務能提交。事務提交完成後會調用cleanupAfterCompletion清除當前事務的狀態,如果有掛起的事務還會通過resume恢復掛起的事務(將解綁的連接和當前線程綁定以及將之前保存的事務狀態重新設置回去)。當前事務正常提交後,那麼就會輪到addA方法提交,處理邏輯同上,不再贅述。
如果調用addB發生異常,就會通過completeTransactionAfterThrowing進行回滾:

	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);
			}
			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
				try {
					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
				}
			}
		}
	}

	public final void rollback(TransactionStatus status) throws TransactionException {
		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
		processRollback(defStatus, false);
	}

	private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
		try {
			boolean unexpectedRollback = unexpected;

			try {
				triggerBeforeCompletion(status);

				//按照嵌套事務按照回滾點回滾
				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Rolling back transaction to savepoint");
					}
					status.rollbackToHeldSavepoint();
				}
				//都爲PROPAGATION_REQUIRED最外層事務統一回滾
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction rollback");
					}
					doRollback(status);
				}
				else {
					// Participating in larger transaction
					if (status.hasTransaction()) {
						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
							}
							doSetRollbackOnly(status);
						}
						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) {
				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 {
			cleanupAfterCompletion(status);
		}
	}

流程和提交是一樣的,先是判斷有沒有回滾點,如果有就回到到回滾點並清除該回滾點;如果沒有則判斷是不是新事務(PROPAGATION_REQUIRED屬性下的最外層事務PROPAGATION_REQUIRES_NEW屬性下的事務),滿足則直接回滾當前事務。回滾完成後同樣需要清除掉當前的事務狀態並恢復掛起的連接。另外需要特別注意的是在catch裏面調用完回滾邏輯後,還通過throw拋出了異常,這意味着什麼?意味着即使是嵌套事務,內層事務的回滾也會導致外層事務的回滾,也就是addA的事務也會跟着回滾。
至此,事務的傳播原理分析完畢,深入看每個方法的實現是很複雜的,但如果僅僅是分析各個傳播屬性對事務的影響,則有一個簡單的方法。我們可以將內層事務切面等效替換invocation.proceedWithInvocation方法,比如上面兩個類的調用可以看作是下面這樣:

// addA的事務
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
	// addB的事務
	TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
	Object retVal = null;
	try {
		retVal = invocation.proceedWithInvocation();
	}
	catch (Throwable ex) {
		// target invocation exception
		//事務回滾
		completeTransactionAfterThrowing(txInfo, ex);
		throw ex;
	}
	finally {
		cleanupTransactionInfo(txInfo);
	}
	//事務提交
	commitTransactionAfterReturning(txInfo);
}
catch (Throwable ex) {
	//事務回滾
	completeTransactionAfterThrowing(txInfo, ex);
	throw ex;
}
//事務提交
commitTransactionAfterReturning(txInfo);

這樣看是不是很容易就能分析出事務之間的影響以及是提交還是回滾了?下面來看幾個實例分析。

實例分析

我再添加一個C類,和addC的方法,然後在addA裏面調用這個方法。

TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
	// addB的事務
	TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
	Object retVal = null;
	try {
		b.addB();
	}
	catch (Throwable ex) {
		// target invocation exception
		//事務回滾
		completeTransactionAfterThrowing(txInfo, ex);
		throw ex;
	}
	//事務提交
	commitTransactionAfterReturning(txInfo);

	// addC的事務
	TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
	Object retVal = null;
	try {
		c.addC();
	}
	catch (Throwable ex) {
		// target invocation exception
		//事務回滾
		completeTransactionAfterThrowing(txInfo, ex);
		throw ex;
	}
	//事務提交
	commitTransactionAfterReturning(txInfo);
}
catch (Throwable ex) {
	//事務回滾
	completeTransactionAfterThrowing(txInfo, ex);
	throw ex;
}
//事務提交
commitTransactionAfterReturning(txInfo);

等效替換後就是上面這個代碼,我們分別來分析。

  • 都是PROPAGATION_REQUIRED屬性:通過上面的分析,我們知道三個方法都是同一個連接和事務,那麼任何一個出現異常則都會回滾。
  • addBPROPAGATION_REQUIRES_NEW
    • 如果B中拋出異常,那麼B中肯定會回滾,接着異常向上拋,導致A事務整體回滾;
    • 如果C中拋出異常,不難看出C和A都會回滾,但B已經提交了,因此不會受影響。
  • addCPROPAGATION_NESTEDaddBPROPAGATION_REQUIRES_NEW
    • 如果B中拋出異常,那麼B回滾並拋出異常,A也回滾,C不會執行;
    • 如果C中拋出異常,先是回滾到回滾點並拋出異常,所以A也回滾,但B此時已經提交,不受影響。
  • 都是PROPAGATION_NESTED:雖然創建了回滾點,但是仍然是同一個連接,任何一個發生異常都會回滾,如果不想影響彼此,可以try-catch生吞子事務的異常實現。

還有其它很多情況,這裏就不一一列舉了,只要使用上面的分析方法都能夠很輕鬆的分析出來。

總結

本篇詳細分析了事務的傳播原理,另外還有隔離級別,這在Spring中沒有體現,需要我們自己結合數據庫的知識進行分析設置。最後我們還需要考慮聲明式事務編程式事務的優缺點,聲明式事務雖然簡單,但不適合用在長事務中,會佔用大量連接資源,這時就需要考慮利用編程式事務的靈活性了。總而言之,事務的使用並不是一律默認就好,接口的一致性吞吐量與事務有着直接關係,嚴重情況下可能會導致系統崩潰。

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