Thanks to the transaction management capabilities provided by Spring, we can focus on implementing business logic without having to worry too much about transaction initiation, commit, rollback, propagation, etc.

Today, let’s take a look at what Spring does for us and how it does it.

Spring Transaction Configuration

Let’s start by looking at what core classes Spring uses to help us manage transactions and what they do.

The transaction manager: PlatformTransactionManager

The core class in Spring transaction management.

According to design experience, the more core something is, the simpler it should be. Therefore, the interface only provides three methods.

  1. Return an existing active transaction or create a new one, depending on the current transaction propagation mechanism.
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException;
Copy the code
  1. Commits the specified thing based on the transaction state.
void commit(TransactionStatus status) throws TransactionException;
Copy the code
  1. Rolls back the specified thing.
void rollback(TransactionStatus status) throws TransactionException;
Copy the code

Programmatic transaction entry: TransactionTemplate

Like the other XXxTemplates Spring provides for us, TransactionTemplate is the entry class we use for programmatic transaction management.

In this class, in the form of template methods, we are given an entry point to execute a transaction:

public <T> T execute(TransactionCallback<T> action) throws TransactionException 
Copy the code

Declarative transaction management: TransactionAspectSupport

The Transactional annotation corresponds to an AOP approach to managing the life cycle of a transaction.

protected Object invokeWithinTransaction(Method method, @NullableClass<? > targetClass,final InvocationCallback invocation) throws Throwable {
Copy the code

The transaction attribute TransactionDefinition

PROPAGATION mechanism

Transmission mechanism describe
PROPAGATION_REQUIRED Support current transactions and create a new one if there are none.
PROPAGATION_SUPPORTS Support for the current transaction, if there is no transaction currently executed in the transaction.
PROPAGATION_MANDATORY Supports current transactions and throws an exception if there are none.
PROPAGATION_REQUIRES_NEW Creates a new transaction and suspends the current transaction if one exists.
PROPAGATION_NOT_SUPPORTED The current transaction is not supported and is always executed without a transaction.
PROPAGATION_NEVER Transactions are not supported and an exception is thrown if one is currently present.
PROPAGATION_NESTED Execute as a nested transaction, if one exists currently.

ISOLATION level ISOLATION

Isolation level describe
ISOLATION_READ_UNCOMMITTED Dirty reads, unrepeatable reads, and phantom reads may occur.
ISOLATION_READ_COMMITTED Can avoid dirty read, may occur non-repeatable read, phantom read.
ISOLATION_REPEATABLE_READ Can avoid dirty read, do not repeat read, may occur magic read.
ISOLATION_SERIALIZABLE Can avoid dirty read, non – repeat read, unreal read.

TIMEOUT duration

Read the read – only

The name of the name

Transaction execution process

Here is an example of a programmatic transaction. In Spring, a transaction executes from start to finish.

Perform the entranceexecute

Spring’s programmatic transaction management capabilities are provided by the TransactionTemplate. The entry point for executing a transaction is the execute method, which uses the following structure:

transactionTemplate.execute((TransactionCallback) transactionStatus -> {
     try {
          / / transaction
     }catch(Exception e){ transactionStatus.setRollbackOnly(); }});Copy the code

Let’s go to the entry execute method and see how it works:

public <T> T execute(TransactionCallback<T> action) throws TransactionException {... TransactionStatus status =this.transactionManager.getTransaction(this);
      try{
	  result = action.doInTransaction(status);

      }catch(...). { rollbackOnException(status, ex); . }...this.transactionManager.commit(status);
      return result;

}
Copy the code

From the above code, you can clearly see the entire transaction execution process: get -> execute -> exception rollback/commit

The transactiongetTransaction

* Return a currently active transaction or create a new one, according to
* the specified propagation behavior.
Copy the code

The first step in executing the transaction is to use the transactionManager call getTransaction to retrieve the transaction with the transaction attribute TransactionDefinition described above.

I have simplified the method code as follows:

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
	Object transaction = doGetTransaction();
        
	if (isExistingTransaction(transaction)) {
            return handleExistingTransaction(definition, transaction, debugEnabled);
	}
        if ( PROPAGATION_MANDATORY ){
            throw. }else if (PROPAGATION_REQUIRED || PROPAGATION_REQUIRES_NEW || PROPAGATION_NESTED ){
            try{
                SuspendedResourcesHolder suspendedResources = suspend(null);
                DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                doBegin(transaction, definition);
                prepareSynchronization(status, definition);
                return status;
            }catch( ... ){
                resume(null, suspendedResources);
		throwex; }}return prepareTransactionStatus(definition, null.true, newSynchronization, debugEnabled, null);

Copy the code

Creating a transaction objectdoGetTransaction

* Return a transaction object for the current transaction state.
Copy the code

The logic behind this method is simple:

  1. Simple to createDataSourceTransactionObjectObject.
  2. Try using the current data source as Key fromThreadLocalRemove theCurrent transaction bindingThe connection, if any, is set to the transaction property.

If no connection is placed in the current ThreadLocal, it is not currently in a transaction.

Determines whether a transaction existsisExistingTransaction

* Check if the given transaction object indicates an existing transaction
* (that is, a transaction which has already started).
Copy the code

A transaction object holds a database connection and the corresponding transaction is active.

Process existing transactions according to the propagation mechanismhandleExistingTransaction

* Create a TransactionStatus for an existing transaction.
Copy the code

PROPAGATION_NEVER

Within this branch, exceptions are thrown directly, as defined by the transaction propagation mechanism.

throw new IllegalTransactionStateException(
					"Existing transaction found for transaction marked with propagation 'never'");
Copy the code

PROPAGATION_NOT_SUPPORTED

Need to execute in no transaction. Therefore, existing transactions are suspended within the branch.

Object suspendedResources = suspend(transaction);
Copy the code

PROPAGATION_REQUIRES_NEW

Create a new transaction and suspend the current transaction.

SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
    booleannewSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction,true, newSynchronization, debugEnabled, suspendedResources);
    doBegin(transaction, definition);
    prepareSynchronization(status, definition);
    return status;
}catch(...). { resumeAfterBeginException(transaction, suspendedResources, beginEx);throw beginEx;
}
Copy the code

PROPAGATION_NESTED

Nested transaction execution. Create and store checkpoints.

DefaultTransactionStatus status = prepareTransactionStatus(definition, transaction, false.false, debugEnabled, null);
status.createAndHoldSavepoint();
return status;
Copy the code

PROPAGATION_REQUIRED || PROPAGATION_SUPPORTS || PROPAGATION_MANDATORY

According to the transaction propagation mechanism definition, all three propagation mechanisms behave the same in the case of an existing transaction: they use the existing transaction directly.

return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);

Copy the code

Currently there is no transaction

If there are no transactions, they need to be handled on a case-by-case basis according to different transaction propagation mechanisms.

PROPAGATION_MANDATORY

Throw an exception directly.

throw new IllegalTransactionStateException(
	"No existing transaction found for transaction marked with propagation 'mandatory'");
Copy the code

PROPAGATION_REQUIRED || PROPAGATION_REQUIRES_NEW || PROPAGATION_NESTED

Create a new transaction execution directly.

DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
Copy the code

PROPAGATION_SUPPORTS || PROPAGATION_NOT_SUPPORTED || PROPAGATION_NEVER

Transactions are not supported and do not need to be created.

Perform intra-transaction operationsdoInTransaction

Once the transaction object is retrieved, the business logic wrapped inside the transaction can be executed.

Here we use a template method that simply executes the doInTransaction method in the TransactionCallback passed in by the caller.

result = action.doInTransaction(status);
Copy the code

Abnormal rollbackrollbackOnException

If there is abnormal when perform within a transaction logic, need to invoke the transactionManager. Rollback to roll back.

try {
    this.transactionManager.rollback(status);
}catch(...). {...throw. ; }Copy the code

submitcommit

Under normal circumstances, calls transactionManager.com MIT to commit the transaction.

The transaction actions

opendoBegin

. Code is located in the org. Springframework. JDBC datasource. DataSourceTransactionManager# doBegin this method is really open a transaction, the specific process is as follows:

Transaction bound join

Set the connection to be used within the current transaction object, or create a new one from the data source if there is none.

Note that one of the conditions to determine if you are currently in a transaction is to save a database connection, see isExistingTransaction.

if(! txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Connection newCon = obtainDataSource().getConnection(); txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
Copy the code

Set the value of this object to read-only

Set based on the transaction property passed in.

If read-only is true, the database connection used by the current transaction needs to be set to read-only.

Setting the Isolation Level

Set based on the transaction property passed in.

If the isolation level of the current database connection is not the same as the isolation level passed in when the transaction was started, the isolation level of the current connection needs to be reset.

Turn off automatic submission

In a transaction library transaction, the commit command is called manually.

Therefore, you need to turn off autoCommit for the current connection.

Marks transaction active status

txObject.getConnectionHolder().setTransactionActive(true);
Copy the code

Note that another condition to determine whether you are currently in a transaction is to use a database connection with a transaction active status of true, as shown in isExistingTransaction.

Setting timeout

Set based on the transaction property passed in.

txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
Copy the code

Current thread binding connection

Set the current connection into a ThreadLocal object with key as the data source for the current transaction.

When nested transactions exist, or when the connection is removed from the ThreadLocal for reuse, see doGetTransaction.

TransactionSynchronizationManager.bindResource(obtainDataSource(), 
    txObject.getConnectionHolder());
Copy the code

hangsuspend

* Suspend the given transaction. Suspends transaction synchronization first,
* then delegates to the {@code doSuspend} template method.
Copy the code

In the presence of nested transactions, the visibility of suspend operations is very high and is used in a variety of propagation mechanisms.

And suspend, is to complete the following requirements:

  • A transaction exists, but a subtransaction is set to not execute in a transaction: PROPAGATION_NOT_SUPPORTED
  • A transaction already exists, but the subtransaction needs to execute in a brand new transaction: PROPAGATION_REQUIRES_NEW

Therefore, the current transaction is suspended so that it will not be affected by subsequent operations.

Suspend operations are as follows:

Clears intra-transaction bound connections

txObject.setConnectionHolder(null);
Copy the code

The current thread is unbound from the connection

TransactionSynchronizationManager.unbindResource(obtainDataSource())
Copy the code

As you can see from the steps, transaction suspension means that the commit is not performed, but the resource is unbound from the transaction.

However, suspended resources are always restored. Therefore, a resource that is unbound by suspension cannot be released and needs to be passed to the current TransactionStatus.

Object suspendedResources = suspend(transaction); .return prepareTransactionStatus(definition, null.false, newSynchronization, debugEnabled, suspendedResources);
Copy the code

restoreresume

Corresponding to suspension, the suspended transaction is resumed. Used to start a new transaction after the current transaction is suspended or to restore the old transaction after the new transaction is completed (ROLLBACK/COMMIT).

To resume, of course, is to do the opposite of suspending.

The rollback

Transaction rollback entrance is org. Springframework. Transaction. Support. AbstractPlatformTransactionManager# rollback

Trigger the transaction completion pre-callback

This is an extension point that Spring provides for us.

Checks whether checkpoints are created or rolled back to checkpoints

If the current transaction is checked, Spring will roll back to the checkpoint when we roll back.

Object savepoint = getSavepoint(); . getSavepointManager().rollbackToSavepoint(savepoint); getSavepointManager().releaseSavepoint(savepoint); setSavepoint(null);
Copy the code

Perform rollback/Set rollback

Set the rollback

When the isolation level is one of the following and the current transaction is an inner transaction, only rollback is set and no actual rollback is performed.

Isolation level Do not roll back the cause directly
PROPAGATION_NOT_SUPPORTED The inner layer does not support transactions and does not require rollback
PROPAGATION_NESTED Share transactions with the outer layer, using checkpoint rollback
PROPAGATION_REQUIRED Common transaction with outer layer, inner layer only set rollback, not directly perform rollback.
PROPAGATION_SUPPORTS Same as above
PROPAGATION_MANDATORY Same as above

rollback

Use the connection directly to invoke the database for rollback operations.

Connection con = txObject.getConnectionHolder().getConnection(); . con.rollback();Copy the code

Trigger the post-transaction callback

This is an extension point that Spring provides for us.

Clean up the

Clean up ThreadLocal variables, restore connection autoCommit, isolation level, and other Settings (if needed), and resume pending transactions.

Creating checkpoints

When the transaction isolation level is set to PROPAGATION_NESTED and nested transactions exist, Spring creates checkpoints for us before executing nested transactions.

Checkpoints are a feature provided by the database that allows us to specify a location to roll back to during rollback.

Checkpoints are used only under the PROPAGATION_NESTED isolation level. When an inner transaction is rolled back, it is rolled back to the point where the inner transaction was created, rather than the entire transaction.

@Override
public Object createSavepoint(a) throws TransactionException {
	ConnectionHolder conHolder = getConnectionHolderForSavepoint();
	try {
		if(! conHolder.supportsSavepoints()) {throw. ; }if (conHolder.isRollbackOnly()) {
			throw. ; }return conHolder.createSavepoint();
	}
	catch (SQLException ex) {
		throw. ; }}Copy the code

submit

First of all, the outer layer called the AbstractPlatformTransactionManager# commit does not mean that the transaction commit: if the current transaction has been set up rollbackOnly will rollback.

if (defStatus.isLocalRollbackOnly()) {
	processRollback(defStatus, false);
	return;
}
Copy the code

Preparation before submission

This is an extension point that Spring provides for us.

Trigger a pre-commit callback

This is an extension point that Spring provides for us.

Trigger the transaction completion pre-callback

This is an extension point that Spring provides for us.

Release checkpoint

Because the transaction is about to commit, the checkpoints saved earlier are useless. Checkpoints will be released first (if any).

commit

A COMMIT statement is executed to commit the transaction. However, the actual execution commit is conditional:

if (status.isNewTransaction()) {
    ...
    doCommit(status);
}
Copy the code

Similar to rollback:

Isolation level Do not submit reasons directly
PROPAGATION_NOT_SUPPORTED The inner layer does not support transactions and does not require a commit
PROPAGATION_NESTED Shares transactions with the outer layer, which does not commit and only releases checkpoints
PROPAGATION_REQUIRED Common transaction with outer layer, do not commit directly.
PROPAGATION_SUPPORTS Same as above
PROPAGATION_MANDATORY Same as above

Trigger a post-commit callback

This is an extension point that Spring provides for us.

Trigger the post-transaction callback

This is an extension point that Spring provides for us.

Clean up the

Cleanup with transaction rollback.

Spring transaction implementation summary

First, the transaction management that Spring provides for us is not that mysterious.

This is essentially done by binding a database connection to a transaction object and turning off autoCommit for that connection.

Spring provides us with a rich transaction propagation mechanism, and there are a lot of tricks to implement different propagation mechanisms:

PROPAGATION_REQUIRES_NEW Suspends the original transaction and initiates the execution of the new one: Thus, there is no relationship between the two transactions and they are completely different database connections. Therefore, the rollback of the inner transaction does not affect the outer transaction.

PROPAGATION_NESTED still uses the original transaction, but a checkpoint is created. If an inner transaction is rolled back, only the checkpoint is rolled back, and the outer transaction is not affected. Similarly, if the inner transaction commits, no real commit is actually performed.

other

@startuml start if (existing transaction?) then(yes) split split split :REQUIRED; split again :SUPPORTS; split again :MANDATORY; End split end split: use an existing transaction directly; note right newTransaction = false end note split again split :REQUIRES_NEW; : Suspends the current transaction; : Creates a new transaction; note right newTransaction = true end note split again :NOT_SUPPORTED; : Suspends the current transaction; note left newTransaction = false end note split again :NEVER; end split again :NESTED; #00FFFF: Create checkpoint; note left newTransaction = false end note end split end split else(no) split :MANDATORY; end split again :REQUIRED; split again :NESTED; split again :REQUIRES_NEW; End split #00FA9A: create transaction; Note Right newTransaction = true end note endif: Perform intra-transaction operation; If (transaction completed successfully?) Then (yes) if(check rollbackOnly?) Then (false) partition commit {if(checkpoint exists?) Then (yes) #00FFFF; else if( newTransaction ? ) Then (true) #HotPink: Commit; Rollback {if(rollback) {rollback (rollback) {rollback (rollback) {rollback (rollback) {rollback (rollback) {rollback (rollback) {rollback (rollback) Then (yes) #AAAAAA: rollback to checkpoint; #00FFFF: Release checkpoint; else if( newTransaction ? ) Then (true) #AAAAAA: rollback; Else (false) : Set the rollback flag rollbackOnly = true; Endif} endif else(no) partition rollback {if(rollback exists?) Then (yes) #AAAAAA: rollback to checkpoint; #00FFFF: Release checkpoint; else if( newTransaction ? ) Then (true) #AAAAAA: rollback; Else (false) : Set the rollback flag rollbackOnly = true; Endif} endif: clear; : Resuming pending transactions; stop @endumlCopy the code