1. Database transaction features

We all know the four properties of mysql database transaction ACID

  • A(Atomicity) : Database operations performed by multiple sessions either succeed or fail simultaneously
  • C(Consistency) Consistency: the transaction must change the database from one Consistency state to another Consistency state. That is, a transaction must be in Consistency state before and after execution
  • I(Isolation) Isolation: When multiple users concurrently access the database, for example, when they concurrently operate the same table, the transactions initiated by the database for each user cannot be disturbed by the operations of other transactions. Multiple concurrent transactions must be isolated from each other
  • D(Durability) Durability refers to the fact that once a transaction is committed, changes to data in the database are permanent, even if the database system encounters a failure, the operation for submitting the transaction is not lost.

2. Dirty read, phantom read, unrepeatable read

Dirty read:? A dirty read is when transaction 2 reads data that was not committed by transaction 1

Unrepeatable read:? After transaction 1 reads the data, transaction 2 modifies its data. When transaction 1 reads the data again, it gets different data from the first time.

Phantom read? After transaction 1 reads some data, transaction 2 deletes or adds records to the data. When transaction 1 reads again, it finds that the number of records decreases or increases

Mysql transaction isolation level

The following is the support for dirty reads, phantom reads, and unrepeatable reads by isolation level. By default, the isolation level of mysql is repeatable reads.

Isolation level Dirty read Unrepeatable read Phantom read
READ UNCOMMITTED There are There are There are
READ COMMITTED There is no There are There are
REPEATABLE READ There is no There is no There are
SERIALIZABLE There is no There is no There is no

4. Unsubmitted read generates dirty read

In connection 1, we set the transaction isolation level of the current session to uncommitted read, and then enabled the transaction to insert a record into the database. We did not execute the commit statement manually, and the transaction was in the uncommitted state:

Set the isolation level to uncommitted read in connection 2 and query the table.

In this case, you can see that connection 2 can query the data of connection 1 as committed transaction, so dirty read will be generated

4. Will dirty reads be generated after read is submitted?

We change the isolation level of the database to commit read, start the transaction, insert a data into the table, and do not commit:

Now open link 2 and query:

At this point we can see that connection 2 cannot read the uncommitted data from connection 1.

Summary: mysql provides four isolation levels, each of which provides different solutions for dirty reads, unrepeatable reads, and phantom reads. The more powerful the four isolation levels are, the more powerful they are. The safest level in the table above is serializability, but mysql does not default to this isolation level because the higher the isolation level, the lower the concurrency performance.

5.JDBC transaction control

JDBC is an application program interface in the Java language that regulates how a client program accesses a database. JDBC provides methods for querying and updating data in a database. JDBC, also a trademark of Sun Microsystems (now owned by Oracle), is relational-database oriented.

The original JDBC database connection is as follows:

Public void updateCoffeeSales()throws SQLException {try {// Obtaining the Connection Connection conn = DriverManager.getConnection(DB_URL,USER,PASS); // Disable con.setAutoCommit(false); //DML database operation... // manually commit transaction con.mit (); } catch (SQLException e) {rollback(); } finally {// close the connection, etc.}}Copy the code

In this code block, we need to manually commit a transaction while processing JDBC transactions, and call the rollback method of the connection object when an exception occurs. You can also see here that the most basic dependency of a transaction is a database connection object,

6.Spring transaction support

6.1 Transaction Manager

Instead of implementing transaction management itself, Spring provides an abstract transaction management interface or superclass that needs to be implemented by other DB frameworks. Spring provides the abstract transaction manager interface:

/ / Spring transaction manager top interface public interface TransactionManager {} -- -- -- -- -- -- -- -- -- -- -- -- -- -- the public interface PlatformTransactionManager Extends TransactionManager {TransactionStatus getTransaction(@nullable TransactionDefinition) definition)throws TransactionException; // Commit the transaction void commit(TransactionStatus status) throws TransactionException; // rollback the transaction void rollback(TransactionStatus status) throws TransactionException; }Copy the code
  • Common DataSourceTransactionManager transaction manager inherited class diagram as shown below:

By the above two pictures can see DataSourceTransactionManager manager’s package for Spring – JDBC, DB framework is a third party. Instead of implementing transaction management itself, Spring provides an abstract transaction management interface or superclass that needs to be implemented by other DB frameworks

6.2 Key Terms:
  • PlatformTransactionManager: this is the Spring of a transaction manager abstract interface, the interface defines the foundation method of transaction.
Public interface PlatformTransactionManager extends TransactionManager {/ / return currently active transaction or create a new transaction TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; // Commit the given transaction void commit(TransactionStatus status) throws TransactionException; // rollback the specified transaction void rollback(TransactionStatus status) throws TransactionException; }Copy the code
  • TransactionDefinition: the definition of a transaction, in which the transaction propagation behavior and transaction isolation level are defined, for example:
Public interface TransactionDefinition {//------ TransactionDefinition ------ // Support the current transaction; If one does not exist, create a new one. int PROPAGATION_REQUIRED = 0; // Support the current transaction; If not, it is executed nontransactionally. int PROPAGATION_SUPPORTS = 1; // Support the current transaction; If there is no current transaction, throw an exception int PROPAGATION_MANDATORY = 2; // Create a new transaction and suspend the current transaction (if one exists). int PROPAGATION_REQUIRES_NEW = 3; // Do not support the current transaction; Instead, it always executes nontransactionally. int PROPAGATION_NOT_SUPPORTED = 4; // Do not support the current transaction; Throw an exception int PROPAGATION_NEVER = 5 if the transaction is current. // Execute in a nested transaction if the current transaction exists, int PROPAGATION_NESTED = 6; //------ transaction isolation level ------ // Use the default isolation level of the underlying data store. int ISOLATION_DEFAULT = -1; Int ISOLATION_READ_UNCOMMITTED = 1; Int ISOLATION_READ_COMMITTED = 2; Int ISOLATION_REPEATABLE_READ = 4; // serialization int ISOLATION_SERIALIZABLE = 8; // Use the default timeout of the underlying transaction system int TIMEOUT_DEFAULT = -1; Default int getPropagationBehavior() {return PROPAGATION_REQUIRED; } // Default transaction isolation level default int getIsolationLevel() {return ISOLATION_DEFAULT; }}Copy the code
  • SavepointManager: Transaction save node, when there are multiple database modification operations in a method, we do not want to roll back all operations, but only want to roll back to a node, in this case, we need to manually create the rollback node, SavepointManager interface provides three methods (specific implementation is DB framework) as follows:
Public interface SavepointManager {// Create a new savepoint. You can roll back to a specific savepoint Object createSavepoint() throws TransactionException; // Roll back to the given savepoint. void rollbackToSavepoint(Object savepoint) throws TransactionException; // Explicitly release the given savepoint. void releaseSavepoint(Object savepoint) throws TransactionException; }Copy the code
  • TransactionExecution: A generic representation of the current state of a transaction.
Public interface TransactionExecution {// Returns whether the current transaction is a new transaction; boolean isNewTransaction(); // Set transaction rollback only void setRollbackOnly(); Boolean isRollbackOnly(); // Returns whether the transaction is marked for rollback only. // Returns whether the transaction completed, Boolean isCompleted(); }Copy the code
  • TransactionStatus: TransactionStatus
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable // hasSavepoint(); hasSavepoint(); // refresh @override void flush(); }Copy the code
6.2Spring programmatic transactions

Spring recommends using the TransactionTemplate interface to implement programmatic transactions:

@RequestMapping("test") public class TestTx { @Autowired private TransactionTemplate transactionTemplate; @Autowired private IResourceAdminService iResourceAdminService; @GetMapping() public void test(){ transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { try { ResourceAdmin resourceAdmin = new ResourceAdmin(); ResourceAdmin. Elegantly-named setName (" zhang "); resourceAdmin.setPassword("123456"); / / database save operation Boolean save = iResourceAdminService. Save (resourceAdmin); If (save){log.info(" database saved 1 successfully "); } int I =5/0; }catch (Exception e){ e.printStackTrace(); status.setRollbackOnly(); Log.error (" abnormal rollback transaction occurred "); }}}); }}Copy the code
6.3 Excute methods provided in the shallow TransactionTemplate source code:
@Override @Nullable public <T> T execute(TransactionCallback<T> action) throws TransactionException { Assert.state(this.transactionManager ! = null, "No PlatformTransactionManager set"); if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); } else {/ / 1. According to the current transaction manager access transaction state TransactionStatus status = this. TransactionManager. GetTransaction (this); T result; Result = action.doinTransaction (status); } catch (RuntimeException | Error ex) { // Transactional code threw application exception -> rollback //3. RollbackOnException (status, ex); throw ex; } catch (Throwable ex) { // Transactional code threw unexpected exception -> rollback rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); } / / 4. The transaction manager to commit the transaction this.transactionManager.com MIT (status); return result; }}Copy the code
The argument to the excute method is a functional interface:

The doInTransaction(TransactionStatus Status) method is the one we need to rewrite

@functionalInterface public interface TransactionCallback<T> {// Override this method when using excute methods @nullable T doInTransaction(TransactionStatus status); }Copy the code
RollbackOnException (status,ex)
private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException { Assert.state(this.transactionManager ! = null, "No PlatformTransactionManager set"); logger.debug("Initiating transaction rollback on application exception", ex); Try {/ / using the transaction manager will roll this. TransactionManager. Rollback (status); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by rollback exception", ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error("Application exception overridden by rollback exception", ex); throw ex2; }}Copy the code

The rollbackOnException(status,ex) method also calls the manager’s rollback method to rollback the transaction

Create rollback transactions based on transaction nodes

The SavepointManager interface has three methods, one of which creates a transaction node and rolls back to the specified transaction node. The following example demonstrates the use of these two methods:

Object savepoint=null; try { ResourceAdmin resourceAdmin = new ResourceAdmin(); ResourceAdmin. Elegantly-named setName (" zhang "); resourceAdmin.setPassword("123456"); / / database save operation Boolean save = iResourceAdminService. Save (resourceAdmin); If (save){log.info(" database saved 1 successfully "); Savepoint = status.createsavePoint (); savePoint = status.createsavePoint (); ResourceAdmin. Elegantly-named setName (" li si "); / / database to store the save operation = iResourceAdminService. Save (resourceAdmin); If (save){log.info(" database saved 2 successfully "); } int I =5/0; }catch (Exception e){ e.printStackTrace(); Log.error (" abnormal rollback transaction occurred "); / / rollback to specify the transaction node status. The rollbackToSavepoint (savepoint); }Copy the code

In the above code block, we create a transaction node after saving The triple user successfully, and then save the triple user. In the exception handling module, before rolling back to save the triple user, if the code runs successfully, there will only be triple user in the database, so run the test:

From the figure above we can see that the transaction is indeed rolled back to save the user.

Supplement: Actually in the above example we rewrite doInTransactionWithoutResult method, if catch exceptions in the way we do not call status. The setRollbackOnly () method to roll back the transaction, The TransactionTemplate template method also helps us with transaction rollback and commit. The diagram below:

Our transaction code is actually placed in action.doinTransaction (status); So if we call the excute method and forget to roll back, the template method will automatically commit and roll back the transaction for us, so there are no blocking problems.

6.5 Declarative Transactions:

In daily development, I believe that most people use declarative transactions. Many people, including the author, also know that aop is used to implement the principle, but do not really understand this idea. Let’s look at the implementation from source code.

TransactionAttribute interface: TransactionAttribute

Public interface TransactionAttribute extends TransactionDefinition {// returns the value of the qualifier associated with this TransactionAttribute. This can be used to select the corresponding transaction manager, @nullable String getQualifier(); // This may be used to apply specific transactional behavior or to follow a purely descriptive nature. Collection<String> getLabels(); Boolean rollbackOn(Throwable ex); }Copy the code

TransactionDefinition inherits from TransactionDefinition and adds three abstract methods.

The transaction attribute interface: TransactionAttributeSource

Public interface TransactionAttributeSource {/ / to determine whether a given Class of transaction attribute, the candidate Class default Boolean isCandidateClass (Class <? > targetClass) { return true; } @nullable TransactionAttribute getTransactionAttribute(Method Method, @nullable Class<? > targetClass); }Copy the code

Transaction information class: TransactionInfo

Protected the static final class TransactionInfo {/ / transaction manager @ Nullable private final PlatformTransactionManager transactionManager; @nullable private Final TransactionAttribute TransactionAttribute; // The fully qualified name of the method private Final String joinpointIdentification; @nullable private TransactionStatus TransactionStatus; }Copy the code

The TransactionInfo class is an internal class in the TransactionAspectSupport class. For space reasons, only a few member methods of the TransactionInfo class are shown here.

Method interceptor:

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
	@Nullable
	Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}

Copy the code

TransactionInterceptor: TransactionInterceptor

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable { public TransactionInterceptor(TransactionManager ptm, TransactionAttributeSource tas) { setTransactionManager(ptm); setTransactionAttributeSource(tas); } @Override @Nullable 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... return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() { @Override @Nullable public Object proceedWithInvocation() throws Throwable { return  invocation.proceed(); } @Override public Object getTarget() { return invocation.getThis(); } @Override public Object[] getArguments() { return invocation.getArguments(); }}); }}Copy the code

TransactionInterceptor, which implements the method interceptor and overwrites the invoke method of the method interceptor.

Look at the invokeWithTransaction() method inside the invoke method:

In the invoke method method we mainly look at the invokeWithinTransaction() method:

@Nullable protected Object invokeWithinTransaction(Method method, @Nullable Class<? > targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas ! = null ? tas.getTransactionAttribute(method, targetClass) : null); / / get the transaction manager final PlatformTransactionManager tm = determineTransactionManager (txAttr); Final String joinpointIdentification = methodIdentification(Method, targetClass, txAttr); if (txAttr == null || ! (tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. TransactionInfo txInfo = createTransactionIfNecessary(tm, 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. retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // target invocation exception completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; }... }Copy the code

MethodIdentification (Method, targetClass, txAttr); Method implementation:

private String methodIdentification(Method method, @Nullable Class<? > targetClass, @Nullable TransactionAttribute txAttr) { String methodIdentification = methodIdentification(method, targetClass); if (methodIdentification == null) { if (txAttr instanceof DefaultTransactionAttribute) { methodIdentification = ((DefaultTransactionAttribute) txAttr).getDescriptor(); } if (methodIdentification == null) { methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass); } } return methodIdentification; }Copy the code

Follow up ClassUtils. GetQualifiedMethodName (method, targetClass); Methods:

// Returns the qualified name of the given method, consisting of the fully qualified interface/class name + ". Public static String qualifiedMethodName (Method, @qualifiedClass <? > clazz) { Assert.notNull(method, "Method must not be null"); return (clazz ! = null ? clazz : method.getDeclaringClass()).getName() + '.' + method.getName(); }Copy the code

In this method, the qualified name of the given method is returned, consisting of the fully qualified interface/class name + “. + method name.

Moving on to the source code, see the method circled in red in the image below

createTransactionIfNecessary(tm, txAttr, joinpointIdentification)

// If necessary, According to the given TransactionAttribute create a transaction protected TransactionInfo createTransactionIfNecessary (@ Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { // If no name specified, apply method identification as transaction name. if (txAttr ! = null && txAttr.getName() == null) { txAttr = new DelegatingTransactionAttribute(txAttr) { @Override public String getName() { return joinpointIdentification; }}; } TransactionStatus status = null; if (txAttr ! = null) { if (tm ! = null) { status = tm.getTransaction(txAttr); } else { if (logger.isDebugEnabled()) { logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "]  because no transaction manager has been configured"); } } } return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); }Copy the code

Here we can understand for createTransactionIfNecessary method is actually a create a transaction

Invocation. ProceedWithInvocation () : this is surrounded by a notification method, the next interceptor invocation chain, this method is to perform for us be @ Transaction annotations mark method

CompleteTransactionAfterThrowing () method:

In the figure above we can clearly see two familiar lines of code: the rollback() and commit() methods

The cleanupTransactionInfo() method is used to remove transaction information from ThreadLocal

// Reset TransactionInfo ThreadLocal. protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) { if (txInfo ! = null) { txInfo.restoreThreadLocalStatus(); }}Copy the code

CommitTransactionAfterReturning (txInfo) method:

// Execute after the call completes successfully, not after the exception has been handled. If we don't have to create a transaction, you don't do anything protected void commitTransactionAfterReturning (@ Nullable TransactionInfo txInfo) {if (txInfo! = null && txInfo.getTransactionStatus() ! = null) { if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); } txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); }}Copy the code

Commit (); commit(); commit(); commit();

if (txAttr == null || ! (tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. // 1. Create a transaction TransactionInfo txInfo = createTransactionIfNecessary (tm, 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. //retVal = invocation.proceedWithInvocation(); Execute code block identified by transaction annotation} catch (Throwable ex) {//3. An exception occurs rollback / / target invocation exception completeTransactionAfterThrowing (txInfo, ex); throw ex; } finally {// 4. CleanupTransactionInfo (txInfo); } / / 5. Commit the transaction / / commitTransactionAfterReturning (txInfo); return retVal; }Copy the code

From the simplification of the above code blocks, we know that declarative transactions also create transactions in the code, execute transaction operation code blocks, roll back/commit transactions. It is the Spring framework that helps us encapsulate its operation method, and its essence is still based on the transaction manager connection object to execute transactions. The biggest feature of declarative transactions is the connection between AOP and transactions.

This article briefly explains the support for Spring transactions, programmatic transactions, and declarative transactions at the source level, which is the tip of the iceberg of Spirng transaction knowledge. After learning this article, I believe that readers can also learn and strengthen the knowledge of Spring transactions.