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.
- 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
- Commits the specified thing based on the transaction state.
void commit(TransactionStatus status) throws TransactionException;
Copy the code
- 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:
- Simple to create
DataSourceTransactionObject
Object. - Try using the current data source as Key from
ThreadLocal
Remove 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