02_Spring transaction framework source code

Spring transaction framework source code

1. A rough analysis of @Transactional

Suppose there is the following case now.

public class ServiceA {
	@Autowired
	private class ServiceB serviceB;
	
	public void test() {
		serviceB.method();
	}
}

public class ServiceB {
	@Transactional(rollback = Exception.class)
	public void method() {
		...
	}
}

ServiceA injected through @Autowired is actually a dynamic proxy object generated by Spring based on ServiceB. When ServiceA calls the method() method of ServiceB, first the dynamic proxy object will find that there is an @Transactional annotation on the method to be called, so it will treat this method as an AOP aspect. Before calling method(), perform a preamble Enhance the logic, the purpose is to start the transaction (start transaction). Then carefully observe the execution process of the method. If an error is found, the current transaction is rolled back. If the execution ends normally, after the method() method ends, a post-enhanced logic is executed for the purpose of committing the transaction.

The whole process is shown in the figure below

Insert picture description here

2. Explore Spring-tx.jar

The dependency library for Spring to manage transactions is spring-tx.jar, with a focus on org.springframework.transaction.interceptor.TransactionInteceptor.

We already know that when a method of the target object is called, the request will actually be intercepted by the TransactionInteceptor. This process looks a lot like a JDK dynamic proxy, so the request must be passed to the interceptor's invoke() method.

In the invoke() method, the focus is on invokeWithinTransaction(), we only need to pay attention to the core code in this method, the structure is very clear.

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
	// [第一步] 创建并开启事务
	TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
	Object retVal = null;
	try {
		// [第二步]执行目标方法的业务逻辑
		retVal = invocation.proceedWithInvocation();
	}
	catch (Throwable ex) {
		// [第四步]如果报错,则"考虑"回滚事务
		completeTransactionAfterThrowing(txInfo, ex);
		throw ex;
	}
	finally {
	    // 一些事务的清理工作
		cleanupTransactionInfo(txInfo);
	}
	// [第三步]能走到这儿,说明上面所有的步骤都没有报错 所以,提交事务
	commitTransactionAfterReturning(txInfo);
	return retVal;
}

So, the next step is to carefully analyze the source code around these four steps.

2.1 Create and start a transaction

  1. spring-tx.jar TransactionAspectSupport createTransactionIfNecessary()
    Core code: PlatformTransactionManager #getTransaction()
  2. spring-tx.jar AbstractPlatformTransactionManager getTransaction()
    Core code 1: Object transaction = doGetTransaction();
    Core code 2: doBegin(transaction, definition);
  3. spring-orm.jar JpaTransactionManager doGetTransaction()
    gets a JpaTransactionObject object.
  4. spring-orm.jar JpaTransactionManager doBegin()
    core code: getJpaDialect().beginTransaction()
    where getJpaDialect() gets HibernateJpaDialect from hibernate. So far, we can see that the bottom layer of spring transaction is connected with hibernate Relationship.
  5. spring-orm.jar HibernateJpaDialect beginTransaction()
    core code: entityManager.getTransaction().begin();
  6. hibernate-entitymanager.jar TransactionImpl begin()
    core code: Transaction tx = getSession().beginTransaction();
  7. hibernate-core.jar beginTransaction()
  8. hibernate-core.jar AbstractLogicalConnectionImplementor begin()
    between the seventh and eighth steps, some steps are skipped, because it is already at the bottom level, there are too many layers, and it makes no sense to list all the codes. We only need to focus on the useful code.
	@Override
	public void begin() {
		try {
			log.trace( "Preparing to begin transaction via JDBC Connection.setAutoCommit(false)" );
			getConnectionForTransactionManagement().setAutoCommit( false );
			status = TransactionStatus.ACTIVE;
			log.trace( "Transaction begun via JDBC Connection.setAutoCommit(false)" );
		}
		catch( SQLException e ) {
			throw new TransactionException( "JDBC begin transaction failed: ", e );
		}
	}

In the above code, getConnectionForTransactionManagement() returns java.sql.Connection, so we translate this code:

From the database connection pool prepared in advance, obtain a connection (Connection) that has established a channel with Mysql, and set it to "turn off automatic submission", and at the same time, open the transaction. The bottom layer is that the Mysql driver sends the corresponding instructions to Mysql.

2.2 The business logic of executing the target method

Now that the transaction has been opened, the next step is to execute the target business logic. The code to execute the target method is

spring-tx.jar TransactionAspectSupport invocation.proceedWithInvocation()

In fact, it is through reflection to call the target method of the class being proxied.

2.3 Commit the transaction

If the business logic reports no errors, then commit the transaction.

  1. spring-tx.jar TransactionAspectSupport invokeWithinTransaction()
    This is the main entrance to execute transaction processing.
  2. spring-tx.jar TransactionAspectSupport commitTransactionAfterReturning(txInfo);
    When all the steps such as opening the transaction and executing the business logic are completed, and no errors are reported, then start to commit the transaction.
  3. spring-tx.jar TransactionAspectSupport commitTransactionAfterReturning()
    core method: txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
  4. spring-tx.jar AbstractPlatformTransactionManager commit()
    core method: processCommit(defStatus);
  5. spring-orm.jar JpaTransactionManager doCommit()
    starts to execute JpaTransactionManager commit()
  6. hibernate-core.jar AbstractLogicalConnectionImplementor commit()
    omit some steps from step 5 to step 6,
@Override
public void commit() {
	try {
		log.trace( "Preparing to commit transaction via JDBC Connection.commit()" );
		getConnectionForTransactionManagement().commit();
		status = TransactionStatus.COMMITTED;
		log.trace( "Transaction committed via JDBC Connection.commit()" );
	}
	catch( SQLException e ) {
		status = TransactionStatus.FAILED_COMMIT;
		throw new TransactionException( "Unable to commit against JDBC Connection", e );
	}

	afterCompletion();
}

The core code is: getConnectionForTransactionManagement().commit();
In fact, the transaction is submitted through java.sql.Connection #commit().

2.4 Transaction rollback

If an error is reported when the business logic of the target method is executed, according to the code of invokeWithinTransaction(), we know that the error will be caught, and then execute
completeTransactionAfterThrowing(txInfo, ex); to roll back the transaction.

The entire rollback process is basically the same as the process of committing a transaction. First, it is processed by Spring's TransactionManager, and then hibernate's entitymanager is found for processing. Finally, the bottom layer is still based on the rollback() method of JDBC Connection to complete the transaction rollback.

Taken from AbstractLogicalConnectionImplementor.java

@Override
public void rollback() {
	try {
		log.trace( "Preparing to rollback transaction via JDBC Connection.rollback()" );
		getConnectionForTransactionManagement().rollback();
		status = TransactionStatus.ROLLED_BACK;
		log.trace( "Transaction rolled-back via JDBC Connection.rollback()" );
	}
	catch( SQLException e ) {
		throw new TransactionException( "Unable to rollback against JDBC Connection", e );
	}

	afterCompletion();
}

2.5 Summary

Spring's transaction mechanism is very simple, which can be summarized in a paragraph: When a class calls the specified method of the target object, it actually calls the dynamic proxy object provided by Spring. Since the target object is found to be annotated with @Transactional, it is used The idea of ​​AOP inserts a piece of enhanced logic (obviously a surround enhancement) for the target method of the dynamic proxy object, so the request will be intercepted by the TransactionInterceptor and the invoke() method will be executed. First, open the transaction with the help of JDBC Connection, the default transaction is not automatically submitted, and then, through reflection to execute the specified method of the actual object, the execution of the entire business logic is all covered in the transaction, if an error is reported, use the rollback() of the JDBC Connection Roll back the transaction, otherwise you will use the commit() of JDBC Connection to submit the transaction. After the transaction is committed, the relevant operations on the database are truly reflected in the database.

The code details above seem to be cumbersome. In fact, it is nothing more than Spring's transaction manager interacting with Hibernate's code. Hibernate finds JDBC Connection to complete the final operations, such as opening transactions, transaction rollbacks, and transaction commits. With so much code, there is actually only one method at the core:

Taken from TransactionAspectSupport invokeWithinTransaction(), the logic is very clear

		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.
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// target invocation exception
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

Draw the above content into a picture, you can zoom in and view

Insert picture description here