spring transaction-declarative transaction and programmatic transaction

spring transaction-declarative transaction and programmatic transaction

1. Database transaction characteristics

We all know the four major characteristics of mysql database transaction ACID

  • A (Atomicity) atomicity: multiple sessions for database operations, either succeed at the same time or fail at the same time
  • C (Consistency) consistency: refers to the transaction must make the database from a consistent state to another consistent state, that is to say, a transaction must be in a consistent state before and after execution
  • I (Isolation) isolation: When multiple users access the database concurrently, such as operating the same table at the same time, the transaction opened by the database for each user cannot be interfered by the operation of other transactions. Multiple concurrent transactions Must be isolated from each other
  • D (Durability) Durability: Durability means that once a transaction is committed, the changes to the data in the database are permanent, even if the database system encounters a failure, the operation of committing the transaction will not be lost .

2. Dirty reading, phantom reading, non-repeatable reading

Dirty read:? Dirty read is transaction 2 read the uncommitted data of transaction 1

Non-repeatable reading:? After transaction 1 reads the data, transaction 2 modifies its data. When transaction 1 reads the data again, it will get different data from the first read.

Phantom reading? After transaction 1 reads some data, transaction 2 deletes or adds records to its data. When transaction 1 reads again, it is found that the number of records has decreased or increased

3.mysql transaction isolation level

The following is the support of dirty read, phantom read, and non-repeatable read in each isolation level. By default, the isolation level of mysql is repeatable read.

Isolation levelDirty readNon-repeatablePhantom reading
Uncommitted read (READ UNCOMMITTED)existexistexist
READ COMMITTEDdoes not existexistexist
Repeatable read (REPEATABLE READ)does not existdoes not existexist
Serializable (SERIALIZABLE)does not existdoes not existdoes not exist

4. Demonstration case of dirty reads caused by uncommitted reads

In connection 1, we set the current session transaction isolation level to uncommitted read, and then opened the transaction to insert a record into the database without manually executing the commit statement. At this time, the transaction is in an uncommitted state:

At this time, set the isolation level in connection 2 to uncommitted read and query the data table. At this time, the result is:

At this point, you can see that connection 2 can query the data of connection 1 as the submitted transaction, and dirty read

4. Will the committed reads produce dirty reads?

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

At this point, we open connection 2 again and query:

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

Summary: MySQL provides 4 isolation levels, each level provides a different solution to dirty reads, non-repeatable reads, and phantom reads. These 4 isolation levels are top-down (in the above table) and can solve the problem. The stronger the ability. In the above table, the safest level is serializable, but why the default isolation level of mysql is not it? The reason is that the higher the isolation level, the lower its concurrency performance.

5. JDBC transaction control

JDBC is an application program interface used to regulate how client programs access the database in the Java language, and provides methods for querying and updating data in the database. JDBC is also a trademark of Sun Microsystems (now part of Oracle), and is for relational databases.

The most primitive JDBC database connection method is as follows:

public void updateCoffeeSales()throws SQLException { try { //Get connection Connection conn = DriverManager.getConnection(DB_URL,USER,PASS); //Turn off automatic submission con.setAutoCommit(false); //DML database operation ... //Manually commit the transaction con.commit(); } catch (SQLException e) { //An abnormal rollback occurred con.rollback(); } finally { //Close the connection and other operations } } Copy code

In this code block, we need to manually commit the transaction when JDBC processes the transaction, and call the rollback method of the connection object to perform the rollback operation when an exception occurs. It can also be seen here that the most basic dependency of a transaction is the database connection object,

6. Spring transaction support

6.1 Transaction Manager

Spring does not implement transaction management by itself, but gives an abstract transaction management interface or superclass, which requires other db frameworks to implement it. The abstract transaction manager interface provided by Spring:

//Top-level interface of transaction manager provided by Spring public interface TransactionManager { } -------------- public interface PlatformTransactionManager extends TransactionManager { //Obtain the transaction status according to the transaction definition TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; //Submit the transaction void commit(TransactionStatus status) throws TransactionException; //Roll back the transaction void rollback(TransactionStatus status) throws TransactionException; } Copy code
  • The common DataSourceTransactionManager transaction manager inheritance class diagram is as follows:

From the above two figures, it can be seen that the package where the DataSourceTransactionManager manager is located is Spring-jdbc, which is a third-party DB framework. Spring does not implement transaction management by itself, but gives an abstract transaction management interface or superclass, which requires other db frameworks to implement it.

6.2 Explanation of key terms:
  • PlatformTransactionManager: This is an abstract interface of a transaction manager provided by Spring, which defines basic transaction methods.
public interface PlatformTransactionManager extends TransactionManager { //Return to the currently active transaction or create a new transaction TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; //Submit a given transaction void commit(TransactionStatus status) throws TransactionException; //Roll back the specified transaction void rollback(TransactionStatus status) throws TransactionException; } Copy code
  • TransactionDefinition: The definition of a transaction, which defines some transaction propagation behaviors and transaction isolation levels such as:
public interface TransactionDefinition { //------Transaction propagation behavior------ //Support the current transaction; if it does not exist, create a new one. int PROPAGATION_REQUIRED = 0; //Support the current transaction; if it does not exist, execute it in a non-transactional manner. 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 it exists). int PROPAGATION_REQUIRES_NEW = 3; //The current transaction is not supported; instead, it is always executed in a non-transactional manner. int PROPAGATION_NOT_SUPPORTED = 4; //The current transaction is not supported; if the current transaction, an exception is thrown int PROPAGATION_NEVER = 5; //If the current transaction exists, it will be executed in a nested transaction, int PROPAGATION_NESTED = 6; //------Transaction isolation level------ //Use the default isolation level of the underlying data storage. int ISOLATION_DEFAULT = -1; //Uncommitted read int ISOLATION_READ_UNCOMMITTED = 1; //has been submitted for reading int ISOLATION_READ_COMMITTED = 2; //Can be read repeatedly int ISOLATION_REPEATABLE_READ = 4; //Serialization int ISOLATION_SERIALIZABLE = 8; //Use the default timeout of the underlying transaction system int TIMEOUT_DEFAULT = -1; //Provide the default transaction propagation behavior method in the interface default int getPropagationBehavior() { return PROPAGATION_REQUIRED; } //Default transaction isolation level default int getIsolationLevel() { return ISOLATION_DEFAULT; } } Copy code
  • SavepointManager: Transaction save node. When there are multiple database modification operations in a method, we do not want to roll back all operations when an exception occurs, but only want to roll back to a node. At this time, we need to manually create a rollback node. 3.methods are provided in the SavepointManager interface (the specific implementation is the DB framework) as follows:
public interface SavepointManager { //Create a new savepoint. Can roll back to a specific save point Object createSavepoint() throws TransactionException; //Roll back to the given save point. void rollbackToSavepoint(Object savepoint) throws TransactionException; //Explicitly release the given savepoint. void releaseSavepoint(Object savepoint) throws TransactionException; } Copy code
  • TransactionExecution: A general representation of the current state of the transaction.
public interface TransactionExecution { //Return whether the current transaction is a new transaction; boolean isNewTransaction(); //Set the transaction to roll back only void setRollbackOnly(); //Return whether the transaction has been marked as rollback only boolean isRollbackOnly(); //Return whether the transaction is completed, boolean isCompleted(); } Copy code
  • TransactionStatus: Representation of transaction status
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable //Is there a save node inside the transaction boolean hasSavepoint(); //refresh @Override void flush(); } Copy code
6.2 Spring programmatic transaction

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.setName("Zhang San"); resourceAdmin.setPassword("123456"); //Database save operation boolean save = iResourceAdminService.save(resourceAdmin); if(save){ log.info("Database saved 1 successfully"); } //Simulate exception int i=5/0; }catch (Exception e){ e.printStackTrace(); status.setRollbackOnly(); log.error("An exception occurred to roll back the transaction"); } } }); } } Copy code
6.3 A brief introduction to the excute method provided in the 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. Get the transaction status according to the current transaction manager TransactionStatus status = this.transactionManager.getTransaction(this); T result; try { //2. Transaction operation result = action.doInTransaction(status); } catch (RuntimeException | Error ex) { //Transactional code threw application exception -> rollback //3. Exception rollback 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 commits the transaction this.transactionManager.commit(status); return result; } } Copy code
The parameter of the excute method is a functional interface:

The doInTransaction(TransactionStatus status) method is what we need to rewrite

@FunctionalInterface public interface TransactionCallback<T> { //When using the excute method, you need to rewrite this method @Nullable T doInTransaction(TransactionStatus status); } Copy code
RollbackOnException(status,ex) method implementation:
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 { //Use the transaction manager to roll the transaction 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 code

The principle of the rollbackOnException(status,ex) method is also to call the rollback method of the manager to roll back the transaction

Create a rollback transaction based on the transaction node

In the above, we mentioned the SavepointManager interface. There are three methods in this interface, including a method to create a transaction node and roll back to a specified transaction node. The following example will demonstrate the usage of these two methods:

Object savepoint=null; try { ResourceAdmin resourceAdmin = new ResourceAdmin(); resourceAdmin.setName("Zhang San"); resourceAdmin.setPassword("123456"); //Database save operation boolean save = iResourceAdminService.save(resourceAdmin); if(save){ log.info("Database saved 1 successfully"); } //Create transaction node savepoint = status.createSavepoint(); resourceAdmin.setName(" "); //Database save operation save = iResourceAdminService.save(resourceAdmin); if(save){ log.info("Database saved 2 successfully"); } //Simulate exception int i=5/0; }catch (Exception e){ e.printStackTrace(); log.error("An exception occurred to roll back the transaction"); //Roll back to the specified transaction node status.rollbackToSavepoint(savepoint); } Copy code

In the above code block, after successfully saving the user Zhang San, we created a transaction node, and then saved the user Li Si. In the exception handling module, we rolled back to save Li Si. If the code runs successfully, the database will only There are three users named Zhang, run the test:

From the above figure, we can find that the transaction is indeed rolled back to the time before saving Li Si user.

Supplement: In fact, when we rewrite the doInTransactionWithoutResult method in the above example, if we do not call the status.setRollbackOnly() method to roll back the transaction when we catch the exception in the method, the TransactionTemplate template method will also help us roll back and submit the transaction. As shown below:

Our transaction operation code is actually run in action.doInTransaction(status);, just embed our own database operation statement into the method, so if we forget to perform the rollback operation when we call the excute method, this template method is also Will automatically help us commit and roll back the transaction, so it will not cause problems such as blocking.

6.5 Declarative transaction:

In daily development, I believe that most people use declarative transactions. Many people, including the author, also know that the principle is implemented using aop, but they have not really clarified this idea. Next, let's analyze its implementation from the source code.

Transaction attribute interface: TransactionAttribute

public interface TransactionAttribute extends TransactionDefinition { //Return the qualifier value associated with this transaction attribute. This can be used to select the corresponding transaction manager to handle this particular transaction @Nullable String getQualifier(); //This may be used for application-specific transaction behavior or follow a purely descriptive nature. Collection<String> getLabels(); //Rollback method boolean rollbackOn(Throwable ex); } Copy code

After the above noun analysis, I believe that everyone has a general outline of the TransactionDefinition interface. TransactionAttribute inherits TransactionDefinition and adds three new abstract methods.

Transaction attribute interface: TransactionAttributeSource

public interface TransactionAttributeSource { //Determine whether the given class is a candidate class for transaction attributes default boolean isCandidateClass(Class<?> targetClass) { return true; } //Return the transaction attributes of the given method, @Nullable TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass); } Copy code

Transaction information class: TransactionInfo

protected static final class TransactionInfo { //Transaction Manager @Nullable private final PlatformTransactionManager transactionManager; //Transaction attributes @Nullable private final TransactionAttribute transactionAttribute; //Fully qualified naming of the method private final String joinpointIdentification; //Transaction status @Nullable private TransactionStatus transactionStatus; } Copy code

The TransactionInfo class is an internal class in the TransactionAspectSupport class. Due to space issues, 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 code

Transaction Interceptor: 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 code

The most important thing in declarative transactions is the transaction interceptor TransactionInterceptor. From the above code, we can also see that it implements the method interceptor and rewrites the invoke method in the method interceptor.

Mainly look at the invokeWithTransaction() method in the invoke method:

In the invoke 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 transaction manager final PlatformTransactionManager tm = determineTransactionManager(txAttr); //Get the fully qualified name of the pointcut method 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 code

The important code part of the method is posted above, let's look at the implementation of methodIdentification(method, targetClass, txAttr); method:

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 code

Follow up ClassUtils.getQualifiedMethodName(method, targetClass); method:

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

In this method, the qualified name of the given method will be returned, consisting of fully qualified interface/class name + "." + method name.

Let's continue to look at the source code and see the method circled by the red box in the figure below

createTransactionIfNecessary(tm, txAttr, joinpointIdentification)

//If necessary, create a transaction according to the given TransactionAttribute 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 code

Here we can understand that the createTransactionIfNecessary method is actually a method of creating a transaction

invocation.proceedWithInvocation(): This is a wraparound notification method that calls the next interceptor in the chain. This method actually executes the method marked by the @Transaction annotation for us

The completeTransactionAfterThrowing() method is implemented:

In the picture above, we can clearly see that two familiar codes are rollback() and commit() methods.

The implementation of cleanupTransactionInfo() method: as shown in the figure below, this method mainly removes transaction information from ThreadLocal

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

commitTransactionAfterReturning(txInfo) method:

//Execute after the call is successfully completed, not after the exception is handled. If we did not create a transaction, we do nothing 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 code

The last sentence in the above code also calls the commit() method of the transaction manager. After looking at the execution of each method step by step, we can slightly simplify the above code as follows:

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

Through the simplification of the above code blocks, we know that declarative transactions also create transactions in the code, execute transaction operation code blocks, and roll back/commit transactions. It is the Spring framework that helps us encapsulate its operation methods, and its essence is still based on transaction management. The connection object of the device to perform the transaction. The biggest feature of declarative transactions is to associate AOP with transactions.

This article briefly explains the underlying implementation of Spring transaction support, programmatic transactions and declarative transactions from the source level. This is also the tip of the iceberg of Spirng transaction knowledge. After studying in this article, I believe that readers can also learn and reinforce Spring transactions. knowledge.