The interviewer asked me to name two @Transactional annotated invalid scenarios, and I told him six in one breath

introduction

The @Transactional annotation is not unfamiliar to everyone. A commonly used annotation in normal development, it can ensure that multiple database operations in a method either succeed or fail at the same time. You need to pay attention to a lot of details when using **@Transactional annotation, otherwise you will find that @Transactional** always fails inexplicably.

Below we thoroughly understand how to answer the interviewer’s questions from four aspects: what, where, and when .

1. What is a transaction (WHAT)

Transaction generally refers to something to be done or done. In computer terms, it refers to a program execution unit (unit) that accesses and may update various data items in the database.

Here we explain with an example of withdrawing money: For example, if you go to an ATM to withdraw 1,000 yuan, there are roughly two steps: the first step is to enter the password amount, and the bank card deducts 1,000 yuan; the second step is to pay 1,000 yuan from the ATM . Both of these steps must be executed or neither. If the bank card deducts 1,000 yuan but the ATM fails to pay, you will lose 1,000 yuan; if the bank card deduction fails but the ATM pays 1,000 yuan, then the bank will lose 1,000 yuan.

How to ensure that one of these two steps will not be abnormal, and the other will be executed successfully? Transaction is used to solve such problems. A transaction is a series of actions. They are integrated together to form a complete unit of work. These actions must all be completed. If one fails, the transaction will roll back to the initial state, as if nothing happened. . In the development of enterprise applications, transaction management is an indispensable technology to ensure data integrity and consistency.

In our daily development, the affairs are divided into declarative affairs and programmatic affairs.

Programmatic transaction

Refers to the manual management of transaction commit and rollback operations in the code, and the code is relatively intrusive.

Programmatic transactions refer to the implementation of transactions through coding, allowing users to precisely define the boundaries of transactions in the code.

That is similar to JDBC programming to achieve transaction management. Manage the use of TransactionTemplate or directly use the underlying PlatformTransactionManager.

For programmatic transaction management, Spring recommends using TransactionTemplate.

try {
    
    
   //TODO something
    transactionManager.commit(status);
} catch (Exception e) {
    
    
   transactionManager.rollback(status);
   throw new InvoiceApplyException("异常");
}

Declarative transaction

Management is based on AOP. Its essence is to intercept before and after the method, and then create or add a transaction before the target method starts, and submit or roll back the transaction according to the execution after the target method is executed.

The biggest advantage of declarative transactions is that there is no need to manage transactions through programming, so that there is no need to dope transaction management code in the business logic code. You only need to make relevant transaction rule declarations in the configuration file (or through @Transactional Annotation), you can apply transaction rules to business logic.

Simply put, programmatic transactions invade the business code, but provide more detailed transaction management;

The declarative transaction is based on AOP, so it can not only play the role of transaction management, but also does not affect the specific implementation of the business code.

There are also two ways to implement declarative transactions, one is based on TX and AOP xml configuration files, and the other is based on @Transactional annotations.

@GetMapping("/user")
@Transactional
public String user() {
    
    
       int insert = userMapper.insert(userInfo);
}

2. Where can @Transactional be used (WHERE)

1. Where can the @Transactional annotation be used?

@Transactional can act on interfaces, classes, and class methods .

  • Acting on the class : When the @Transactional annotation is released on the class, it means that all public methods of the class are configured with the same transaction attribute information.
  • Acting on methods : When the class is configured with @Transactional and the method is also configured with @Transactional, the transaction of the method will override the transaction configuration information of the class.
  • Acting on the interface : This method of use is not recommended, because once it is marked on the Interface and Spring AOP is configured to use the CGLib dynamic proxy, it will cause the @Transactional annotation to become invalid
@Transactional
@RestController
@RequestMapping
public class MybatisPlusController {
    
    
   @Autowired
   private UserMapper userMapper;
   
   @Transactional(rollbackFor = Exception.class)
   @GetMapping("/user")
   public String test() throws Exception {
    
    
       User user = new User();
       user.setName("javaHuang");
       user.setAge("2");
       user.setSex("2");
       int insert = userMapper.insert(cityInfoDict);
       return insert + "";
  }
}

2. @Transactional attribute detailed explanation

propagation attribute

propagation propagation behavior on behalf of the transaction, the default value Propagation.REQUIRED , other attribute information as follows:

  • Propagation.REQUIRED : If there is currently a transaction, then join the transaction, if there is no current transaction, then create a new transaction. (That is to say, if the A method and the B method are both annotated, in the default propagation mode, the A method internally calls the B method, and the transactions of the two methods will be merged into one transaction)
  • Propagation.SUPPORTS : If there is a transaction currently, then join the transaction; if there is no transaction currently, continue to run in a non-transactional manner.
  • Propagation.MANDATORY : If there is a transaction currently, it will join the transaction; if there is no transaction currently, an exception will be thrown.
  • Propagation.REQUIRES_NEW : re-create a new transaction, if there is a current transaction, suspend the current transaction. (When the a method in class A uses the default Propagation.REQUIRED mode, the b method in class B plus the Propagation.REQUIRES_NEW mode, and then the b method is called in the a method to operate the database, but after the a method throws an exception, the b method There is no rollback, because Propagation.REQUIRES_NEW will suspend the transaction of method a)
  • Propagation.NOT_SUPPORTED : Run in a non-transactional manner. If there is a current transaction, suspend the current transaction.
  • Propagation.NEVER : Run in a non-transactional manner. If there is currently a transaction, an exception will be thrown.
  • Propagation.NESTED : Same effect as Propagation.REQUIRED.

isolation property

isolation : The isolation level of the transaction, the default value is Isolation.DEFAULT .

TransactionDefinition.ISOLATION_DEFAULT
This is the default value, which means that the default isolation level of the underlying database is used. For most databases, this value is usually

TransactionDefinition.ISOLATION_READ_UNCOMMITTED
The isolation level indicates that a transaction can read data modified by another transaction but has not yet been committed. This level cannot prevent dirty reads, non-repeatable reads, and phantom reads, so this isolation level is rarely used. For example, PostgreSQL does not actually have this level.

TransactionDefinition.ISOLATION_READ_COMMITTED
This isolation level means that a transaction can only read data that has been committed by another transaction. This level can prevent dirty reads, which is also the recommended value in most cases.

TransactionDefinition.ISOLATION_REPEATABLE_READ
This isolation level indicates that a transaction can execute a query multiple times during the entire process, and the records returned are the same each time. This level can prevent dirty reads and non-repeatable reads.

TransactionDefinition.ISOLATION_SERIALIZABLE
all transactions are executed one by one, so that there is no interference between transactions, that is, this level can prevent dirty reads, non-repeatable reads, and phantom reads. But this will seriously affect the performance of the program. Normally, this level is not used either.

timeout attribute

timeout : The timeout period of the transaction, the default value is -1. If the time limit is exceeded but the transaction has not been completed, the transaction is automatically rolled back.

readOnly attribute

readOnly : Specify whether the transaction is a read-only transaction, the default value is false; in order to ignore those methods that do not require transactions, such as reading data, you can set read-only to true.

rollbackFor attribute

rollbackFor : Used to specify the type of exception that can trigger the rollback of the transaction, and multiple types of exceptions can be specified.

noRollbackFor attribute

noRollbackFor : Throw the specified exception type, do not roll back the transaction, you can also specify multiple exception types.

3. When will @Transactional fail (WHEN)

The interviewer directly asked me if I have used @Transactional. I definitely can’t say that I haven’t used it before. I am confident that it is commonly used.

The interviewer asked me again, have you ever encountered @Transactional failures during the actual development process? I definitely can’t say no. I said it very confidently again, often.

The interviewer has a question mark on his face, often? ? ? Then tell me when @Transactional will fail?

The following content is that I have sorted out the failure scenarios that I said during the interview.

1. @Transactional is applied to non-public modified methods

If the Transactional annotation is applied to a method that is not publicly modified, Transactional will become invalid.

The reason for failure is that when Spring AOP is proxying, TransactionInterceptor (transaction interceptor) intercepts before and after the target method is executed. The intercept method of DynamicAdvisedInterceptor (internal class of CglibAopProxy) or the invoke method of JdkDynamicAopProxy will indirectly call the computeTransactionAttribute` method of AbstractFallbackTransactionAttributeSource. Get transaction configuration information of Transactional annotation.

protected TransactionAttribute computeTransactionAttribute(Methodmethod,
   Class<?> targetClass) {
    
    
       // Don't allow no-public methods as required.
       if (allowPublicMethodsOnly() &&!Modifier.isPublic(method.getModifiers())) {
    
    
       return null;
}

Modifier.isPublic will check whether the modifier of the target method is public. If it is not public, it will not get the attribute configuration information of @Transactional.

Note: The @Transactional annotation is used on protected and private modified methods. Although the transaction is invalid, there will be no error. This is a point that we are tolerant of making mistakes.

2. The database engine does not support transactions

The database engine must support transactions. If it is MySQL, note that the table must use an engine that supports transactions, such as innodb. If it is myisam, transactions will not work.

3. @Due to the incorrect setting of propagation, the annotation is invalid

When interpreting the propagation attribute above, we know

TransactionDefinition.PROPAGATION_SUPPORTS
If there is currently a transaction, then join the transaction; if there is no current transaction, continue to run in a non-transactional manner.

TransactionDefinition.PROPAGATION_NOT_SUPPORTED
runs in non-transactional mode. If there is a transaction currently, the current transaction is suspended.

TransactionDefinition.PROPAGATION_NEVER
runs in a non-transactional manner. If there is a transaction currently, an exception is thrown.

When we set the propagation attribute to the above three, the @Transactional annotation will not have an effect

4. The rollbackFor setting is wrong, and the @Transactional annotation is invalid

When we interpret the rollbackFor attribute above, we know

rollbackFor can specify the type of exception that can trigger a transaction rollback.

By default, Spring throws an unchecked unchecked exception (an exception inherited from RuntimeException) or Error to roll back the transaction;

Other exceptions will not trigger a rollback transaction. If you throw other types of exceptions in the transaction, but expect Spring to roll back the transaction, you need to specify the rollbackFor attribute.

// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor=MyException.class

If the exception thrown in the target method is a subclass of the exception specified by rollbackFor, the transaction will also be rolled back. The Spring source code is as follows:

private int getDepth(Class<?> exceptionClass, int depth) {
    
    
   if (exceptionClass.getName().contains(this.exceptionName)) {
    
    
       // Found it!
       return depth;
}
   // If we've gone as far as we can go and haven't found it...
   if (exceptionClass == Throwable.class) {
    
    
       return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}

5. Mutual calls between methods will also cause @Transactional to fail

Let's look at the following scenario:

For example, there is a class User, one of its methods A, and A then calls method B of this class (regardless of whether method B is modified with public or private), but method A does not declare annotated transactions, while method B does. After method A is called externally, the transaction of method B will not work. This is also a place where mistakes are often made.

Why does this happen? In fact, this is still caused by the use of Spring AOP proxy, because only when the transaction method is called by code outside the current class, it will be managed by the proxy object generated by Spring .

  //@Transactional
   @GetMapping("/user")
   private Integer A() throws Exception {
    
    
       User user = new User();
       user.setName("javaHuang");
       /**
        * B 插入字段为 topJavaer的数据
        */
       this.insertB();
       /**
        * A 插入字段为 2的数据
        */
       int insert = userMapper.insert(user);

       return insert;
  }

   @Transactional()
   public Integer insertB() throws Exception {
    
    
       User user = new User();
       user.setName("topJavaer");
       return userMapper.insert(user);
  }

6. The exception is "eaten" by your catch, causing @Transactional to fail

This situation is the most common @Transactional annotation invalidation scenario.

  @Transactional
  private Integer A() throws Exception {
    
    
      int insert = 0;
      try {
    
    
          User user = new User();
          user.setCityName("javaHuang");
          user.setUserId(1);
          /**
            * A 插入字段为 javaHuang的数据
            */
          insert = userMapper.insert(user);
          /**
            * B 插入字段为 topJavaer的数据
            */
          b.insertB();
      } catch (Exception e) {
    
    
          e.printStackTrace();
      }
  }

If method B throws an exception, and method A try to catch the exception of method B at this time, then the transaction cannot be rolled back normally, but an exception will be thrown

org.springframework.transaction.UnexpectedRollbackException:
Transactionrolled back because it has been marked as rollback-only

Solution:

Add rollback='exception' when the first transaction is declared
, manually roll back in the second cath code block

to sum up

We often use the @Transactional annotation, but often we just know that it is a transaction annotation. Many times when the transaction annotation fails, we are all at a loss. We can't see why it takes a long time. Can't solve it.

Through this article, I learned about the invalidation scenario of @Transactional annotation. When you encounter this situation in the future, you can basically see through it at a glance, and then touch your smooth forehead, soga, so easy!

Mom no longer has to worry that I can't find the bug I wrote.

Reprinted from: Java Architect's Road to God User Column
Original link: https://segmentfault.com/a/1190000022219486

Guess you like

Origin blog.csdn.net/qq_35448165/article/details/108762248