Spring中@Transactional 使用及源碼分析

Spring中@Transactional 使用及源碼分析


在Spring開發中,經常會用到這個註解,但它是怎麼起到開啓事務的作用的,還一知半解,只知道應該是基於AOP的,今天就來分析一波它的用法和具體實現。

編程式事務和聲明式事務

​ Spring 支持兩種事務管理方式:編程式和聲明式,編程式是在程序中顯式地使用TransactionTemplate來控制事務的開啓和提交。

​ 聲明式事務是使用@Transactional註解,在類或方法上使用這個註解,就可以起到開啓事務的作用,聲明式事務是基於AOP的方式,在方法前開啓一個事務,在方法執行後進行commit,中間進行一些異常的判斷和處理。

​ 相比來說,聲明式事務使用起來更加優雅,AOP的方式對代碼沒有侵入性,比較推薦在日常開發中使用。

聲明式事務的用法

@Transactional註解可以加在類或方法上,加在類上時是對該類的所有public方法開啓事務。加在方法上時也是隻對public方法起作用。另外@Transactional註解也可以加在接口上,但只有在設置了基於接口的代理時纔會生效,因爲註解不能繼承。所以該註解最好是加在類的實現上。

​ 下面看一下@Transactional註解的各項參數。

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

timtout

timtout是用來設置事務的超時時間,可以看到默認爲-1,不會超時。

isolation

isolation屬性是用來設置事務的隔離級別,數據庫有四種隔離級別:讀未提交、讀已提交、可重複讀、可串行化。MySQL的默認隔離級別是可重複讀。

public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),  // 讀未提交
    READ_COMMITTED(2), 		// 讀已提交
    REPEATABLE_READ(4), 	// 可重複讀
    SERIALIZABLE(8);			// 可串行化
}

readOnly

​ readOnly屬性用來設置該屬性是否是隻讀事務,只讀事務要從兩方面來理解:它的功能是設置了只讀事務後在整個事務的過程中,其他事務提交的內容對當前事務是不可見的。

​ 那爲什麼要設置只讀事務呢?它的好處是什麼?可以看出它的作用是保持整個事務的數據一致性,如果事務中有多次查詢,不會出現數據不一致的情況。所以在一個事務中如果有多次查詢,可以啓用只讀事務,如果只有一次查詢就無需只讀事務了。

​ 另外,使用了只讀事務,數據庫會提供一些優化。

​ 但要注意的是,只讀事務中只能有讀操作,不能含有寫操作,否則會報錯。

propagation

propagation屬性用來設置事務的傳播行爲,對傳播行爲的理解,可以參考如下場景,一個開啓了事務的方法A,調用了另一個開啓了事務的方法B,此時會出現什麼情況?這就要看傳播行爲的設置了。

public enum Propagation {
    REQUIRED(0), 				// 如果有事務則加入,沒有則新建
    SUPPORTS(1),				// 如果已有事務就用,如果沒有就不開啓(繼承關係)
    MANDATORY(2),				// 必須在已有事務中
    REQUIRES_NEW(3),		// 不管是否已有事務,都要開啓新事務,老事務掛起
    NOT_SUPPORTED(4),   // 不開啓事務
    NEVER(5),						// 必須在沒有事務的方法中調用,否則拋出異常
    NESTED(6);					// 如果已有事務,則嵌套執行,如果沒有,就新建(和REQUIRED類似,和REQUIRES_NEW容易混淆)
}

REQUIRES_NEW 和 NESTED非常容易混淆,因爲它們都是開啓了一個新的事務。我去查詢了一下它們之間的區別,大概是這樣:

REQUIRES_NEW是開啓一個完全的全新事務,和當前事務沒有任何關係,可以單獨地失敗、回滾、提交。並不依賴外部事務。在新事務執行過程中,老事務是掛起的。

NESTED也是開啓新事務,但它開啓的是基於當前事務的子事務,如果失敗的話單獨回滾,但如果成功的話,並不會立即commit,而是等待外部事務的執行結果,外部事務commit時,子事務纔會commit。

rollbackFor

在@Transactional註解中,有一個重要屬性是roolbackFor,這是用來判斷在什麼異常下會進行回滾的,當方法內拋出指定的異常時,進行事務回滾。rollbackForClassName也是類似的。

​ rollbackFor有個問題是默認情況會做什麼,以前認爲默認會對所有異常進行回滾,但其實默認情況下只對RuntimeException回滾。

noRollbackFor

這個和上面正好相反,用來設置出現指定的異常時,不進行回滾。

聲明式事務的實現機制

Spring在調用事務增強器的代理類時會首先執行TransactionInterceptor進行增強,在TransactionInterceptor的invoke方法中完成事務邏輯。首先看下TransactionInterceptor的類圖結構。

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

在invoke方法裏,調用了父類的模板方法invokeWithinTransaction,下面我們看下TransactionAspectSupport類。

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);
  	// 獲取PlatformTransactionManager的實現類,底層事務的處理實現都是由PlatformTransactionManager的實現類實現的,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
  	// 切點標識
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
      // 這裏是根據事務的傳播行爲屬性去判斷是否創建一個事務
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, 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.
        // around增強,這裏是執行的回調的目標方法
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// target invocation exception
        // 異常回滾
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
        // 清理信息
				cleanupTransactionInfo(txInfo);
			}
      // 提交事務
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

		else {
			final ThrowableHolder throwableHolder = new ThrowableHolder();

			// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
			try {
				Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
					TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
					try {
						return invocation.proceedWithInvocation();
					}
					catch (Throwable ex) {
						if (txAttr.rollbackOn(ex)) {
							// A RuntimeException: will lead to a rollback.
							if (ex instanceof RuntimeException) {
								throw (RuntimeException) ex;
							}
							else {
								throw new ThrowableHolderException(ex);
							}
						}
						else {
							// A normal return value: will lead to a commit.
							throwableHolder.throwable = ex;
							return null;
						}
					}
					finally {
						cleanupTransactionInfo(txInfo);
					}
				});

				// Check result state: It might indicate a Throwable to rethrow.
				if (throwableHolder.throwable != null) {
					throw throwableHolder.throwable;
				}
				return result;
			}
			catch (ThrowableHolderException ex) {
				throw ex.getCause();
			}
			catch (TransactionSystemException ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
					ex2.initApplicationException(throwableHolder.throwable);
				}
				throw ex2;
			}
			catch (Throwable ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
				}
				throw ex2;
			}
		}
	}

createTransactionIfNecessary

可以看到,這個invokeWithinTransaction方法已經包含了事務執行的整個流程,這裏是使用了模板模式,具體的實現交給子類去實現。下面我們分析一下其中的重要方法。

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 {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
							"] because no transaction manager has been configured");
				}
			}
		}
  	// 返回一個TransactionInfo
		return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
	}

getTransaction

下面們看下用來用來獲取TransactionStatus的getTransaction方法,這個方法是在AbstractPlatformTransactionManager抽象類中,

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
  	// 這裏是開啓一個事務,不同的事務類型(JPA  kafka jta等有不同的實現)
		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());
		}

  	// 不存在事務,但上面講了PROPAGATION_MANDATORY要求必須已有事務,則拋出異常
		// 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);
				doBegin(transaction, definition);
				prepareSynchronization(status, definition);
				return status;
			}
			catch (RuntimeException | Error ex) {
				resume(null, suspendedResources);
				throw ex;
			}
		}
		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);
		}
	}

可以看到getTransaction是用來做了一些事務的初始化工作,包括一些判斷,新建事務等等。

其中一些對已有事務的處理、嵌入式事務的處理的細節,暫時就略過了~

completeTransactionAfterThrowing

下面我們回到TransactionInterceptor,看一下下一個流程:回滾

/**
	 * 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);
			}
			// 這裏是判斷異常類型:默認會判斷RuntimeException和Error,後面會分析
			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;
				}
			}
			// 下面是不滿足回滾條件的,會照樣提交
			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;
				}
			}
		}
	}

上面有個rollbackOn(ex)方法,是用來判斷回滾類型的,我們看下它的實現,它有不同的實現類,看下默認的

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

確實是RuntimeException和Error。

我們接着看一下回滾的實現。

public final void rollback(TransactionStatus status) throws TransactionException {
  // 事務已經完成時,回滾會出現異常
		if (status.isCompleted()) {
			throw new IllegalTransactionStateException(
					"Transaction is already completed - do not call commit or rollback more than once per transaction");
		}

		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
		processRollback(defStatus, false);
	}

processRollback

 * 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 {
				triggerBeforeCompletion(status);
				// 有保存點時候回退到保存點(這裏是指子事務?應該是指嵌套事務吧)
				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Rolling back transaction to savepoint");
					}
					status.rollbackToHeldSavepoint();
				}
				// 如果是新事務,則直接回退
				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);
		}
	}

上面的代碼中,有保存點的是指的嵌套事務,因爲嵌套事務並不是真正的兩個事務,所以會有保存點的信息進行回滾。

事務的提交和回滾的流程類似,同樣進行了不同事務類型的判斷,在此不進行額外的分析。

結語

​ 通過idea的反編譯工具,分析了一波代碼,同時參考了《Spring源碼解析》,練習了一下分析源碼的能力,能大體地看出事務的實現過程,大概看過之後感覺Spring這樣寫是非常合理的,代碼非常清晰,每個功能點都拆分地特別細。博客沒有分析事務的加載,因爲那塊和事務本身的處理並沒有太大的關係,而是Spring對註解等屬性的加載並生成TransactionInterceptor代理對象。

​ 而且Spring裏面使用了非常多的模板方法,使用模板方法對我們的好處是,能從大到小、從整體到局部地瞭解到整個過程,而且大大降低代碼的耦合性。以後在編碼時也會注意多使用模板方法。

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