In depth analysis of Spring transactions and underlying principles

1 knowledge review

1.1 transaction characteristics

https://blog.csdn.net/Mr_YanMingXin/article/details/118857302

1.2 isolation level

https://blog.csdn.net/Mr_YanMingXin/article/details/118857302

1.3 dirty reading, unreal reading and non repeatable reading

https://blog.csdn.net/Mr_YanMingXin/article/details/118857302

2 two ways spring uses transactions

2.1 programmatic transactions

Using TransactionalTemplate

@Autowired
private UserDAO userDAO;

@Autowired
private TransactionTemplate transactionTemplate;

@Override
public void insertUser(User user) {
    transactionTemplate.execute(new TransactionCallback<Object>() {
        //Exceptions do not need to be handled, otherwise they will not be rolled back
        @Override
        public Object doInTransaction(TransactionStatus status) {
            userDAO.insertUser(user);
            return status;
        }
    });
}

2.3 declarative transactions

Use @ Transactional annotation

@Transactional(rollbackFor = Exception.class)
@Override
public int updateUserById(User user) throws Exception {
    int res = userDAO.updateUserById(user);
    return res;
}

2.4 communication mechanism of affairs

Configuration: @ Transactional(propagation = Propagation.xxx)

public enum Propagation {

   /**
    * Support the current transaction. If it does not exist, create a new transaction (Spring's default transaction propagation mechanism)
    */
   REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

   /**
    * Supports the current transaction. If it does not exist, it will be executed in a non transaction manner
    */
   SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

   /**
    * Support the current transaction, and throw an exception if it does not exist
    */
   MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

   /**
    * Create a new transaction. If there is a current transaction, suspend the current transaction
    */
   REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

   /**
    * Execute in a non transactional manner. If there is a current transaction, suspend the current transaction  
    */
   NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

   /**
    * Execute in a non - transactional manner, and throw an exception if there is a transaction
    */
   NEVER(TransactionDefinition.PROPAGATION_NEVER),

   /**
    * If there is a current transaction, it is executed in a nested transaction. Otherwise, the behavior is like {@ code REQUIRED}
    */
   NESTED(TransactionDefinition.PROPAGATION_NESTED);
    
   ...
}
  • REQUIRED: the default propagation mechanism can meet most business requirements. If there are transactions in the outer layer, the current transaction will be added to the outer layer transaction, one committed and one rolled back. If there is no transaction in the outer layer, create a new transaction to execute.
  • REQUES_NEW: the transaction propagation mechanism is to open a new transaction every time and suspend the outer transaction. When the current transaction is completed, the execution of the upper transaction is resumed. If there is no transaction in the outer layer, execute the newly opened transaction.
  • SUPPORT: if there is a transaction in the outer layer, the outer layer transaction is added. If there is no transaction in the outer layer, the non transaction method is directly used. Completely dependent on external transactions.
  • NOT_SUPPORT: this propagation mechanism does not support transactions. If there is a transaction in the outer layer, it will be suspended. After executing the current code, the outer layer transaction will be restored. No matter whether it is abnormal or not, the current code will not be rolled back.
  • NEVER: this propagation mechanism does not support outer transactions, that is, if there are transactions in the outer layer, an exception will be thrown.
  • MANDATORY: Contrary to NEVER, an exception is thrown if there is no transaction in the outer layer.
  • NESTED: the feature of this propagation mechanism is that it can save state savepoints and roll back the current transaction to a certain point, so as to avoid all NESTED transactions rolling back, that is, rolling back their own. If the sub transaction does not eat the exceptions, it will basically cause all rollback.

2.5 isolation level of transactions

Configuration: @ Transactional(isolation = Isolation.xxx)

ISOLATION_DEFAULTUse the default isolation level of the back-end database
ISOLATION_READ_UNCOMMITTEDAllow reading of uncommitted changes. It may cause dirty reading, unreal reading or non repeatable reading.
ISOLATION_READ_COMMITTED(Oracle default level) allows reading from committed concurrent transactions. Dirty reading can be prevented, but phantom reading and non repeatable reading may still occur.
ISOLATION_REPEATABLE_READ(MYSQL default level) the results of multiple reads of the same field are consistent unless the data is changed by the current transaction itself. It can prevent dirty reading and non repeatable reading, but phantom reading can still occur.
ISOLATION_SERIALIZABLECompletely obey the isolation level of ACID to ensure that dirty reads, unrepeatable reads and phantom reads do not occur. This is also the slowest of all isolation levels, because it is usually done by completely locking the data table involved in the current transaction.

2.6 Spring transaction rollback (declarative example)

Configuration: @ Transactional(rollbackFor = Exception.class)

Meaning: the transaction rollback mechanism will be triggered when the exception class specified by rollback for or its derived class occurs

2.6.1 Java exception class inheritance system (part)


By default, the transaction management of the Spring framework only rolls back transactions when uncontrolled exceptions (RuntimeException and Error) occur.

2.6.1 transaction rollback experiment

(1) Using the default configuration, throw a ClassNotFound exception to see whether to roll back

@Transactional
@Override
public int updateUserById(User user) throws Exception {
    int res = userDAO.updateUserById(user);
    throw new ClassNotFoundException();
}

Test:

@Test
void contextLoads() {
    System.out.println("before update "+userService.selectUserById(1));
    try {
        userService.updateUserById(new User(1L, "aaa", "aaa"));
    } catch (Exception e) {
        System.err.println("---------Catch exception---------");
        System.out.println("after update "+userService.selectUserById(1));
    }
}

result:


(2) Configure it as exception.class, throw ClassNotFound exception, and check whether to roll back

@Transactional(rollbackFor = Exception.class)
@Override
public int updateUserById(User user) throws Exception {
    int res = userDAO.updateUserById(user);
    throw new ClassNotFoundException();
}

Test:

@Test
void contextLoads() {
    System.out.println("before update "+userService.selectUserById(1));
    try {
        userService.updateUserById(new User(1L, "bbb", "bbb"));
    } catch (Exception e) {
        System.err.println("---------Catch exception---------");
        System.out.println("after update "+userService.selectUserById(1));
    }
}

result:

(3) Using the default configuration, throw a NullPointException exception to see whether to roll back

@Transactional
@Override
public int updateUserById(User user) throws Exception {
    int res = userDAO.updateUserById(user);
    throw new NullPointerException();
}

Test:

@Test
void contextLoads() {
    System.out.println("before update "+userService.selectUserById(1));
    try {
        userService.updateUserById(new User(1L, "ccc", "ccc"));
    } catch (Exception e) {
        System.err.println("---------Catch exception---------");
        System.out.println("after update "+userService.selectUserById(1));
    }
}

result:

3 bottom layer principle

3.1 general

Spring transaction belongs to the AOP category, which is to conduct transaction processing through the operation of proxy objects on the database, and the underlying layer of spring transaction also needs the support of the database.

Let's debug:

It can be seen that after the transaction is declared, the IOC will use cglib to perform AOP dynamic proxy during initialization, transforming userDAO into a proxy object to operate the transaction

Click enter to see that instead of directly calling the relevant methods of UserDAO, you enter the CglibAopProxy class to proxy the called object


Continue to debug down until an exception occurs

When transaction related information appears, this step is called transaction cleanup

protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {
   if (txInfo != null) {
      // Restore previous state
      txInfo.restoreThreadLocalStatus();
   }
}

3.2 transaction flow

From this, we can summarize the execution process of Spring transactions

Tags: Java Spring Back-end

Posted on Thu, 21 Oct 2021 23:02:51 -0400 by frosty3d