Source code analysis of Spring transaction execution

Keywords: JDBC Attribute Spring Programming

Spring transactions rely on AOP. The transaction execution process is to add AOP facet logic when the business method is executing. It is intercepted by the TransactionInterceptor class. We use this as the entry point to analyze the whole process.

1, 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...
	// Call the parent method directly
	return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

2, TransactionAspectSupport

The invokeWithinTransaction method adds transaction support to the business method through the around aspect. The source code is simplified and easy to analyze.

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

	// If the transaction attribute is null, the method is non-transactional.
	// Get some properties of the configured transaction
	TransactionAttributeSource tas = getTransactionAttributeSource();
	final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
	// <1> , get transaction manager according to property
	final TransactionManager tm = determineTransactionManager(txAttr);

	if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
		// <2> , reactive transaction manager
	}

	// Convert to PlatformTransactionManager
	PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
	final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

	if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
		// Standard transaction demarcation with getTransaction and commit/rollback calls.
		// <3> . standard transaction flow
	}

	else {
		final ThrowableHolder throwableHolder = new ThrowableHolder();

		// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
		// <4> , CallbackPreferringPlatformTransactionManager transaction support process
}

[1] , get the specified transaction manager from the current Spring container. You can get or get the default transaction manager by name.

[2] , transaction support for reactive programming, not in-depth

[3] . standard transaction support process

[4] , CallbackPreferringPlatformTransactionManager transaction support

3, Standard transaction support

This paper mainly analyzes the transaction flow under the background of standard declaration transaction

if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
	// Standard transaction demarcation with getTransaction and commit/rollback calls.
	[1],Get transaction information. This object contains transaction manager, transaction attribute definition and transaction status
	TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

	Object retVal;
	try {
		// This is an around advice: Invoke the next interceptor in the chain.
		// This will normally result in a target object being invoked.
		// Call the target method, our business method, which may contain other AOP s
		retVal = invocation.proceedWithInvocation();
	}
	catch (Throwable ex) {
		// target invocation exception
		// [2] , rollback transaction
		completeTransactionAfterThrowing(txInfo, ex);
		throw ex;
	}
	finally {
		cleanupTransactionInfo(txInfo);
	}

	if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
		// Set rollback-only in case of Vavr failure matching our rollback rules...
		TransactionStatus status = txInfo.getTransactionStatus();
		if (status != null && txAttr != null) {
			retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
		}
	}
    //[3] , commit transaction
	commitTransactionAfterReturning(txInfo);
	return retVal;
}

3.1 obtaining transaction information

This object contains transaction manager, transaction attribute definition and transaction state, including transaction propagation behavior and isolation level implementation.

The core is to obtain the state information of transaction, which includes the implementation of propagation behavior. See: https://my.oschina.net/cregu/blog/3210239

3.2 transaction rollback

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
	if (txInfo != null && txInfo.getTransactionStatus() != null) {
		if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
			// Rollback only if set correctly
			try {
				txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
			}
		}
		else {
			// Rollback is not currently required
			// We don't roll back on this exception.
			// Will still roll back if TransactionStatus.isRollbackOnly() is true.
			try {
				txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
			}
		}
	}
}

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
	try {
		try {
			if (status.hasSavepoint()) {
				// Rollback to savepoint
				status.rollbackToHeldSavepoint();
			}
			else if (status.isNewTransaction()) {
				// The new transaction is rolled back directly. For jdbc, Connection.rollback() is called
				doRollback(status);
			}
			else {
				// Participating in larger transaction
				// There are two situations, one is to join the parent transaction, the other is to run the non transaction
				if (status.hasTransaction()) {
					// Currently joined to parent transaction
					if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
						// For jdbc transactions, the rollbackOnly=true flag is set
						doSetRollbackOnly(status);
					}
					else {
						// Non transactional run, no rollback
						if (status.isDebug()) {
							logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
						}
					}
				}
				else {
					// Non transactional run, no rollback
					logger.debug("Should roll back transaction but cannot - no transaction available");
				}
			}
		}
	}
	finally {
		cleanupAfterCompletion(status);
	}
}

1. Transaction rollback needs to judge the conditions. Rollback can only be performed if the settings are correct;

2. The rollback operation will also depend on the situation and be processed according to different propagation behaviors

3. If it is a new transaction, call jdbc Connection.rollback()

4. If it is a child of the parent transaction, set the rollback flag in the child transaction

5. Non transactional mode and no rollback

3.3 transaction submission

public final void commit(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;
	if (defStatus.isLocalRollbackOnly()) {
		if (defStatus.isDebug()) {
			logger.debug("Transactional code has requested rollback");
		}
		processRollback(defStatus, false);
		return;
	}

	if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
		// Transaction is set with rollback flag, so rollback is needed here
		processRollback(defStatus, true);
		return;
	}

	processCommit(defStatus);
}

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

		try {
			boolean unexpectedRollback = false;
			if (status.hasSavepoint()) {
				if (status.isDebug()) {
					logger.debug("Releasing transaction savepoint");
				}
				unexpectedRollback = status.isGlobalRollbackOnly();
				status.releaseHeldSavepoint();
			}
			else if (status.isNewTransaction()) {
				// The new transaction is committed. jdbc calls Connection.commit();
				if (status.isDebug()) {
					logger.debug("Initiating transaction commit");
				}
				unexpectedRollback = status.isGlobalRollbackOnly();
				doCommit(status);
			}
			else if (isFailEarlyOnGlobalRollbackOnly()) {
				unexpectedRollback = status.isGlobalRollbackOnly();
			}
		}
	}
	finally {
		cleanupAfterCompletion(status);
	}
}

1. Before the transaction is submitted, some judgment will be made. If the child transaction is set with rollback flag, rollback operation is required here

2. If the transaction commit condition is met, the jdbc Connection.commit() is called if it is a new transaction

3. Transaction commit failure will also be rolled back

Posted by passagewds on Tue, 24 Mar 2020 04:06:06 -0700