Say 6 kinds of @ transactional annotation failure scenarios in one breath (Reprint)

Time:2021-12-31

Reprinted from:https://juejin.cn/post/6844904096747503629

introduction

Yesterday, the official account asked a question, saying that he had been questioned before the interview.@TransactionalNote which scenarios will fail, and the interview will fail due to a lack of words. So I’d like to share with you today@TransactionalRelevant knowledge.

@TransactionalAnnotation is believed to be familiar to everyone. It is a commonly used annotation in daily development, which can ensure that multiple database operations in the method either succeed or fail at the same time. use@TransactionalYou need to pay attention to many details when commenting, otherwise you will find@TransactionalIt always fails for some reason.

1、 Business

Transaction management is an indispensable part of system development,SpringIt provides a good transaction management mechanism, which is mainly divided intoProgramming transactionandDeclarative transactionTwo.

Programming transaction: refers to the manual management of transaction submission, rollback and other operations in the code. The code is highly intrusive, as shown in the following example:

try {
    //TODO something
     transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    Throw new invoiceapplyexception ("exception failure");
}

Declarative transaction: Based onAOPAspect oriented, it decouples the specific business from the transaction processing part, and the code intrusion is very low, so declarative transactions are used more in actual development. Declarative transactions can also be implemented in two ways. One is based onTXandAOPThe second is based on @ transactional annotation.

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

2、 @ transactional introduction

1. Where can the @ transactional annotation work?

@Transactional can act onInterfaceclassClass method

  • Act on class: when the @ transactional annotation is placed on a class, it represents all the properties of the classpublicMethods are configured with the same transaction attribute information.
  • Act on method: when the class is configured with @ transactional and the method is configured with @ transactional, the transaction of the method will overwrite the transaction configuration information of the class.
  • Act on interface: this method is not recommended because once it is marked on the interface and spring AOP is configured to use cglib dynamic proxy, the @ transactional annotation will become invalid
@Transactional
@RestController
@RequestMapping
public class MybatisPlusController {
    @Autowired
    private CityInfoDictMapper cityInfoDictMapper;
    
    @Transactional(rollbackFor = Exception.class)
    @GetMapping("/test")
    public String test() 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 + "";
    }
}

2. What are the attributes of @ transactional note?

Propagation property

propagationRepresents the propagation behavior of a transaction. The default value isPropagation.REQUIRED, other attribute information is as follows:

  • Propagation.REQUIRED: if a transaction currently exists, join the transaction. If no transaction currently exists, create a new transaction.(That is, if both method a and method B are annotated, in the default propagation mode, method a calls method B internally, which will merge the transactions of the two methods into one transaction
  • Propagation.SUPPORTS: if a transaction currently exists, join the transaction; If no transaction currently exists, it continues to run in a non transactional manner.
  • Propagation.MANDATORY: if a transaction currently exists, join the transaction; Throw an exception if no transaction currently exists.
  • Propagation.REQUIRES_NEW: re create a new transaction. If there is a current transaction, pause the current transaction.(When the a method in class a uses the defaultPropagation.REQUIREDPattern, method B in class B plus adoptionPropagation.REQUIRES_NEWMode, and then invokes the B method to operate the database in the a method. However, after the a method throws an exception, the B method does not roll back, becausePropagation.REQUIRES_NEWThe transaction of method a is suspended)
  • Propagation.NOT_SUPPORTED: run in a non transactional manner. If there is a current transaction, pause the current transaction.
  • Propagation.NEVER: run in a non transactional manner. If there is a transaction currently, an exception will be thrown.
  • Propagation.NESTED: and propagation Required has the same effect.
Isolation property

isolation: the isolation level of the transaction. The default value isIsolation.DEFAULT

  • Isolation. Default: use the default isolation level of the underlying database.
  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED
  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE
Timeout property

timeout: the timeout of the 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 property

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 property

rollbackFor: used to specify exception types that can trigger transaction rollback. Multiple exception types can be specified.

Norollbackfor property

noRollbackFor: throw the specified exception type, do not roll back the transaction, or specify multiple exception types.

3、 @ transactional failure scenario

Next, we will analyze which scenarios the @ transactional annotation will fail in combination with the specific code.

1. @ transactional is applied to non public modified methods

IfTransactionalAnnotation applied to nonpublicOn the modified method, transactional will fail.

Say 6 kinds of @ transactional annotation failure scenarios in one breath (Reprint)

The reason for the failure is that when using the spring AOP proxy, as shown in the figure aboveTransactionInterceptor(transaction interceptor) intercepts before and after the execution of the target method,DynamicAdvisedInterceptor(internal class of cglibaopproxy) orJdkDynamicAopProxyThe invoke method of is called indirectlyAbstractFallbackTransactionAttributeSourceofcomputeTransactionAttributeMethod to obtain the transaction configuration information of the transactional annotation.

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

This method will check whether the modifier of the target method is public. If it is not public, the property configuration information of @ transactional will not be obtained.

be careful:protectedprivateUsed on the method of modification@TransactionalNote: Although the transaction is invalid, there will be no error, which is a point we are very tolerant of mistakes.

2. @ transactional annotation attribute propagation setting error

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

TransactionDefinition.PROPAGATION_SUPPORTS: if a transaction currently exists, join the transaction; If there are currently no transactions, continue to run in a non transactional manner.TransactionDefinition.PROPAGATION_NOT_SUPPORTED: run in non transactional mode. If there is a transaction, suspend the current transaction.TransactionDefinition.PROPAGATION_NEVER: run in non transactional mode. If there is a transaction currently, an exception will be thrown.

3. @ transactional annotation attribute rollbackfor setting error

rollbackForYou can specify the exception type that can trigger transaction rollback. Spring throws unchecked by defaultuncheckedException (inherited from)RuntimeExceptionOrErrorRollback the transaction; Other exceptions do not trigger a rollback transaction. If you throw other types of exceptions in a transaction, but expect spring to roll back the transaction, you need to specifyrollbackForProperties.

Say 6 kinds of @ transactional annotation failure scenarios in one breath (Reprint)
//You want custom exceptions to be rolled back
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class

If the exception thrown in the target method isrollbackForFor the subclass of the specified exception, the transaction will also be rolled back. The source code of spring 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);
}

4. Method calls in the same class cause @ transactional to fail

During development, it is inevitable to call methods in the same class. For example, there is a class test and its method a, A calls method B of this class again (whether method B is decorated with public or private), but method a does not declare annotation transactions, but method B does. Then after calling method a externally, the transactions of method B will not work. This is also a place where mistakes are often made.

Then why did this happen? In fact, this is still due to the use ofSpring AOPProxy, because only when the transaction method is called by code other than the current classSpringGenerated proxy objects to manage.

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

    return insert;
}

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

    return cityInfoDictMapper.insert(cityInfoDict);
}

5. The exception was “eaten” by your catch, resulting in @ transactional failure

This is the most common @ transactional annotation invalidation scenario,

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

If an exception is thrown inside method B and method a tries to catch the exception of method B, can the transaction be rolled back normally?

Answer: no!

An exception will be thrown:

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

Because whenServiceBAfter throwing an exception in,ServiceBIdentifies the current transaction needsrollback。 howeverServiceABecause you manually catch and handle this exception,ServiceAThink the current transaction should be normalcommit。 At this time, there is inconsistency, that is, because of this, the previous error is thrownUnexpectedRollbackExceptionAbnormal.

springThe transaction of is started before calling the business method and executed after the business method is executedcommit or rollback, whether a transaction is executed depends on whether it is thrownRuntime exception。 If thrownruntime exceptionIf there is no catch in your business method, the transaction will be rolled back.

Generally, catch exceptions are not required in business methods. If you have to catch, you must throw themthrow new RuntimeException(), or specify the exception type in the annotation@Transactional(rollbackFor=Exception.class)Otherwise, it will lead to transaction failure and data inconsistency caused by data commit. Therefore, sometimes try catch will add to the snake.

6. The database engine does not support transactions

The probability of this situation is not high. Whether the transaction can take effect and whether the database engine supports transactions is the key. Common MySQL databases use transaction supported by defaultinnodbEngine. Once the database engine is switched to a database that does not support transactionsmyisam, then the transaction is fundamentally ineffective.

summary

@Transactional annotation seems simple and easy to use, but if you know a little about its usage, you will still step into many pits.

Recommended Today

Could not get a resource from the pool when the springboot project starts redis; nested exception is io. lettuce. core.

resolvent: Find your redis installation path: Start redis server Exe After successful startup: Restart project resolution. ———————————————————————->Here’s the point:<——————————————————————- Here, if you close the redis command window, the project console will report an error. If you restart the project, the same error will be reported at the beginning, The reason is: It is inconvenient to […]