Reprinted from: juejin.cn/post/684490…

The introduction

The Transactional annotation @Transactional was asked in an interview which situations does it fail. So today I’m going to share with you a little bit about @Transactional.

The @Transactional annotation is a common annotation used in development to ensure that multiple database operations within a method will either succeed or fail at the same time. There are a lot of details to pay attention to when using the @Transactional annotation, or you’ll find that @Transactional always fails out of nowhere.

A, the transaction

Transaction management is an indispensable part of system development. Spring provides a good transaction management mechanism, which is mainly divided into programmatic transaction and declarative transaction.

Programmatic transaction: refers to the manual management of transaction submission, rollback and other operations in the code, the code is relatively invasive, as shown in the following example:

try {
    //TODO something
     transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    throw new InvoiceApplyException("Abnormal failure");
}
Copy the code

Declarative transaction: AOP based on the aspect, it will be specific business and transaction processing part decoupled, code invasion is very low, so in the actual development of declarative transaction used more. Declarative transactions can also be implemented using XML configuration files based on TX and AOP, or using the @Transactional annotation.

@Transactional
@GetMapping("/test")
public String test(a) {
    int insert = cityInfoDictMapper.insert(cityInfoDict);
}
Copy the code

Transactional introduction

Where does the @Transactional annotation work?

Transactional can work on interfaces, classes, and class methods.

  • On the classThe @Transactional annotation, when placed ona class, represents all of the Transactional annotations of that classpublicMethods are configured with the same transaction property information.
  • Applied to methods: When a class is configured with @Transactional and a method is configured with @Transactional, the method’s transaction overrides the Transactional configuration information of the class.
  • Use on interfaces: This is not recommended because the @Transactional annotation will fail once annotation is on an Interface and Spring AOP is configured to use CGLib dynamic proxies
@Transactional
@RestController
@RequestMapping
public class MybatisPlusController {
    @Autowired
    private CityInfoDictMapper cityInfoDictMapper;
    
    @Transactional(rollbackFor = Exception.class)
    @GetMapping("/test")
    public String test(a) throws Exception {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setParentCityId(2);
        cityInfoDict.setCityName("2");
        cityInfoDict.setCityLevel("2");
        cityInfoDict.setCityCode("2");
        int insert = cityInfoDictMapper.insert(cityInfoDict);
        return insert + ""; }}Copy the code

What attributes does the @Transactional annotation have?

The propagation properties

Propagation Indicates the propagation behavior of the transaction. The default value is Propagation.REQUIRED.

  • Propagation.REQUIRED: If a transaction currently exists, join the transaction. If no transaction currently exists, create a new transaction.(That is, if method A and method B are annotated, calling method B internally in the default propagation mode will merge the two methods’ transactions into one)
  • Propagation.SUPPORTS: If a transaction exists, join the transaction. If no transaction currently exists, it continues in a non-transactional manner.
  • Propagation.MANDATORY: If a transaction exists, join the transaction. If no transaction currently exists, an exception is thrown.
  • Propagation.REQUIRES_NEW: Creates a new transaction and suspends the current transaction if one exists.(When A method in class A uses the defaultPropagation.REQUIREDPattern, B method of class B plus adoptionPropagation.REQUIRES_NEWSchema, and then call method B in method A to operate on the database, but after method A throws an exception, method B does not roll back, becausePropagation.REQUIRES_NEWSuspends the transaction for method A)
  • Propagation.NOT_SUPPORTED: Runs in a non-transactional manner, suspending the current transaction if one exists.
  • Propagation.NEVER: Runs in a non-transactional manner, throwing an exception if a transaction currently exists.
  • Propagation.NESTEDPropagation.
The isolation properties

Isolation: Isolation level of a transaction. The DEFAULT is Isolation.default.

  • Isolation.DEFAULT: Use the DEFAULT Isolation level of the underlying database.
  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED
  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE
The timeout attribute

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

ReadOnly attribute

ReadOnly: specifies whether the transaction is read-only. The default value is false. To ignore methods that do not require transactions, such as reading data, you can set read-only to true.

RollbackFor properties

RollbackFor: Specifies the type of exception that can trigger a transaction rollback. Multiple exception types can be specified.

NoRollbackFor properties

NoRollbackFor: Throws the specified exception type, does not roll back the transaction, or can specify multiple exception types.

Transactional failure scenario

Let’s take a look at some of the scenarios where the @Transactional annotation fails with specific code.

@Transactional applies to methods that are not public decorates

If the Transactional annotation is applied to a method that is not public decorated, the Transactional annotation becomes invalid.

It fails because in Spring AOP proxying, as shown above, the TransactionInterceptor intercepts the target method before and after execution. DynamicAdvisedInterceptor (CglibAopProxy inner classes) intercept method or JdkDynamicAopProxy invoke method indirect invocation AbstractFallbackTransactionAttributeSource computeTransactionAttribute method, obtain Transactional annotation of transaction configuration information.

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

This method checks if the target method’s modifier is public. Otherwise, it does not get the @Transactional attribute configuration information.

Note:protected,privateThe method of modification is used on@TransactionalNote that even though the transaction is invalid, there will be no errors, which is a big mistake for us.

Error propagation of @transactional attribute

This failure is due to incorrect configuration. If the following three propagation modes are incorrectly configured, the transaction will not be rolled back.

TransactionDefinition. PROPAGATION_SUPPORTS: if a transaction exists, then join the transaction; If there is no transaction currently, it continues in a non-transactional manner. TransactionDefinition. PROPAGATION_NOT_SUPPORTED: run way of transaction, if a transaction exists, suspending the current transaction. TransactionDefinition. PROPAGATION_NEVER: run way of transaction, if the current transaction, throw an exception.

Error setting @transactional annotation rollbackFor

RollbackFor specifies the type of exception that can trigger a transaction rollback. By default, Spring throws unchecked exceptions (exceptions inherited from RuntimeException) or errors to roll back a transaction; Other exceptions do not trigger rollback transactions. If other types of exceptions are thrown in a transaction, but Spring is expected to rollback the transaction, you need to specify the rollbackFor property.

// You want the custom exception to be rolled back
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class
Copy the code

The transaction is also rolled back if the exception thrown in the target method is a subclass of the exception specified by rollbackFor. 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);
}
Copy the code

A method call from the same class causes @transactional to fail

For example, if there is A class Test, A method of A calls method B of the same class (whether method B is public or private), but method A does not declare an annotation transaction, while method B does. Method B’s transaction will not take effect after an external call to method A. This is also where mistakes are often made.

So why is this happening? Again, this is due to the use of Spring AOP proxies, because only when transactional methods are invoked by code other than the current class are they managed by spring-generated proxy objects.

//@Transactional
@GetMapping("/test")
private Integer A(a) throws Exception {
    CityInfoDict cityInfoDict = new CityInfoDict();
    cityInfoDict.setCityName("2");
    /** * B Inserts data with field 3 */
    this.insertB();
    /** * A inserts data with field 2 */
    int insert = cityInfoDictMapper.insert(cityInfoDict);

    return insert;
}

@Transactional()
public Integer insertB(a) throws Exception {
    CityInfoDict cityInfoDict = new CityInfoDict();
    cityInfoDict.setCityName("3");
    cityInfoDict.setParentCityId(3);

    return cityInfoDictMapper.insert(cityInfoDict);
}
Copy the code

The Transactional exception is “eaten” by your catch causing @Transactional to fail

This is the most common @Transactional annotation failure scenario,

@Transactional
private Integer A(a) throws Exception {
    int insert = 0;
    try {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
        cityInfoDict.setParentCityId(2);
        /** * A inserts data with field 2 */
        insert = cityInfoDictMapper.insert(cityInfoDict);
        /** * B Inserts data with field 3 */
        b.insertB();
    } catch(Exception e) { e.printStackTrace(); }}Copy the code

If B throws an exception internally and A tries B’s exception, can the transaction be rolled back normally?

Answer: No!

Will throw an exception:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
Copy the code

Because when an exception is thrown in ServiceB, ServiceB indicates that the current transaction needs to rollback. But in ServiceA, because you manually catch the exception and handle it, the ServiceA assumes that the current transaction should be committed. It will appear inconsistent, that is, because of this, throw the front UnexpectedRollbackException anomalies.

Spring transactions are started before the business methods are called, and commit or rollback is performed after the business methods are executed, depending on whether a Runtime exception is thrown. If a Runtime exception is thrown and there is no catch in your business method, the transaction will be rolled back.

Transactional(rollbackFor= exception.class) ¶ A Transactional(rollbackFor= exception.class) ¶ A Transactional(rollbackFor= exception.class) ¶ Otherwise, the transaction will fail, and the data commit will result in inconsistent data, so sometimes try catch will overkill.

The database engine does not support transactions

The probability of this happening is not very high, and whether the transaction takes effect or not is critical for the database engine to support transactions. Common MySQL databases use the transacts-enabled InnoDB engine by default. Once the database engine switches to myISAM, which does not support transactions, the transactions are essentially invalidated.

conclusion

The @Transactional annotation seems simple and easy to use, but if you don’t know how to use it, you can run into a lot of pitfalls.

Personal website

  • Github Pages
  • Gitee Pages