[Spring] transition: the core object of source code process

Overview

Transaction object

Transaction essence

  • It is also JDBC connection, so the object that the transaction object will eventually save is connection

5-layer package

  • TransationInfo
  • TransationStatus
  • TransationObject
  • ConnectHolder
  • Connection

Inclusion relation

  • TransationInfo -> TransationStatus -> TransationObject -> ConnectHolder -> Connect

Transaction manager

Provide transaction operations: such as append, createSavePoint, resume; commit, rollback, etc

  • AbstractPlatformTransactionManager

Transaction tool class

It contains many TreadLocal objects, because the transaction must be thread private. If multiple threads operate on the same transaction, should a thread be rolled back when an exception occurs?

  • TransactionSynchronizationManager

TransationInfo

  • The core logic of almost all ongoing operations of PlatformTransactionManager transactions is here. It has several subclasses. We mainly discuss the subclass of DataSourceTransactionManager, that is, the management of JDBC connections.
  • Transactiondefinition some configuration information before the transaction is started, such as configuration propagation level, isolation level, timeout control, etc.
  • TransactionStatus the runtime information of the transaction is basically in it, such as connection information, information of the suspended transaction (save point), whether the transaction has run or not, and so on.
  • TransactionInfo - old, each info will contain a reference to its last pending transaction, which may be empty.
  • Joinpointxxx is a string that doesn't matter. It is used for debug ging and contains the information of the transaction aspect.

Core: TransactionStatus

The transaction definition is based on the configuration or the annotation, which defines which platform transaction manager to use. These are relatively simple. The creation and use of TransactionStatus, the most important object of transaction runtime, is relatively complex.

Initialization Trilogy

Its creation can be roughly divided into the following processes:

1. Create

Its creation is relatively simple. There is no factory, so you can create it directly.

	public DefaultTransactionStatus(
			@Nullable Object transaction, boolean newTransaction, boolean newSynchronization,
			boolean readOnly, boolean debug, @Nullable Object suspendedResources) {

		this.transaction = transaction;
		this.newTransaction = newTransaction;
		this.newSynchronization = newSynchronization;
		this.readOnly = readOnly;
		this.debug = debug;
		this.suspendedResources = suspendedResources;
	}

Transaction is an important object inside. It has built-in connection objects. The following is the acquisition process of transaction objects:

	DataSourceTransactionObject txObject = new DataSourceTransactionObject();
	txObject.setSavepointAllowed(isNestedTransactionAllowed());
	ConnectionHolder conHolder =
		(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
	txObject.setConnectionHolder(conHolder, false);
	return txObject;

We can see that the core is the ConnectionHolder, which is particularly important. Note that there is no connection established here, and it is possible to obtain an empty ConnectionHolder. The connected package object ConnectionHolder can control whether the two things use the same connection (the same real transaction, rollback and commit together).

This object is stored in a tool singleton class called TransactionSynchronizationManager, which holds many ThreadLocal to store transaction information.

2. Connection initialization

As mentioned above, there is no real connection. The connection is established in the method of doBegin():

	/**
	 * This implementation sets the isolation level but ignores the timeout.
	 */
	@Override
	protected void doBegin(Object transaction, TransactionDefinition definition) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		Connection con = null;
		
		// The first step is to obtain a new connection according to whether it is managed by a transaction or whether there is a connectionHolder object to create a new connection
		if (!txObject.hasConnectionHolder() ||
				txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
			Connection newCon = obtainDataSource().getConnection();
			if (logger.isDebugEnabled()) {
				logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
			}
			txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
		}

		// Setting this transaction is already under transaction management
		txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
		con = txObject.getConnectionHolder().getConnection();

		// Set some attributes from 'TransactionDefinition', such as isolation level, propagation level, read-only, etc., and set them to available.
		Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
		txObject.setPreviousIsolationLevel(previousIsolationLevel);
		txObject.setReadOnly(definition.isReadOnly());

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

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

The code is long but simple. From top to bottom:

  1. If there is no TransactionHolder or it does not belong to other transaction management, create a new connection obtaindatasource() getConnection();, And put the newly created connection into connectionholder.
  2. Set it to setsynchronizedwithtransaction that is already managed by transaction synchronization (true)
  3. Set some attributes from the TransactionDefinition, such as isolation level, propagation level, read-only, etc., and set them to be available.
  4. Plug it into or back into the ThreadLocal of the transaction synchronization manager, that is, bind the connectionHolder to the current thread.

3. Configure TransactionStatus

The method is as follows, which is very simple but important:

	protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
		if (status.isNewSynchronization()) {
			TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
			TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
					definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
							definition.getIsolationLevel() : null);
			TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
			TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
			TransactionSynchronizationManager.initSynchronization();
		}
	}

We found that it is also the TransactionSynchronizationManager. This transaction information saving object not only saves the connectionHolder, but also saves whether it is a transaction. setActualTransactionActive. For example, if there are transactions in some propagation levels, we use transactions. If not, it is not applicable. spring will still prepare a TransactionInfo for it, but the implementation inside is basically empty.

After that, set some attributes such as propagation level, name and whether it is read-only. Finally, in the initSynchronization() method, create a linkedhashset < transactionsynchronization >, which is called synchronizations. This thing is similar to a listener and will be called when triggering some behaviors of transactions, such as being suspended, being reused, etc

	/**
	 * Suspend this synchronization.
	 * Supposed to unbind resources from TransactionSynchronizationManager if managing any.
	 * @see TransactionSynchronizationManager#unbindResource
	 */
	default void suspend() {
	}

	/**
	 * Resume this synchronization.
	 * Supposed to rebind resources to TransactionSynchronizationManager if managing any.
	 * @see TransactionSynchronizationManager#bindResource
	 */
	default void resume() {
	}

	/**
	 * Flush the underlying session to the datastore, if applicable:
	 * for example, a Hibernate/JPA session.
	 * @see org.springframework.transaction.TransactionStatus#flush()
	 */
	@Override
	default void flush() {
	}
	
	/**
	 * Invoked before transaction commit (before "beforeCompletion").
	 * Can e.g. flush transactional O/R Mapping sessions to the database.
	 * <p>This callback does <i>not</i> mean that the transaction will actually be committed.
	 * A rollback decision can still occur after this method has been called. This callback
	 * is rather meant to perform work that's only relevant if a commit still has a chance
	 * to happen, such as flushing SQL statements to the database.
	 * <p>Note that exceptions will get propagated to the commit caller and cause a
	 * rollback of the transaction.
	 * @param readOnly whether the transaction is defined as read-only transaction
	 * @throws RuntimeException in case of errors; will be <b>propagated to the caller</b>
	 * (note: do not throw TransactionException subclasses here!)
	 * @see #beforeCompletion
	 */
	default void beforeCommit(boolean readOnly) {
	}

	/**
	 * Invoked before transaction commit/rollback.
	 * Can perform resource cleanup <i>before</i> transaction completion.
	 * <p>This method will be invoked after {@code beforeCommit}, even when
	 * {@code beforeCommit} threw an exception. This callback allows for
	 * closing resources before transaction completion, for any outcome.
	 * @throws RuntimeException in case of errors; will be <b>logged but not propagated</b>
	 * (note: do not throw TransactionException subclasses here!)
	 * @see #beforeCommit
	 * @see #afterCompletion
	 */
	default void beforeCompletion() {
	}

TransactionStatus summary

Let's review and summarize TransactionStatus, an important object of spring transaction runtime, member variables and the important methods of platfromtransactionmanger's operation on it, as shown below. For embedded transactions, the propagation level and whether the transaction is the same thing are nothing more than to operate these methods:

Tags: Spring

Posted by jiayanhuang on Sun, 17 Apr 2022 03:46:01 +0930