The use of spring annotation transactions-super complete explanation

1. Business introduction

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

1. Programmatic transactions

Programmatic transactions refer to the manual management of transaction submission, rollback and other operations in the code. The code is relatively intrusive, as shown in the following example:

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

2. Declarative transactions

Declarative transactions are aspect-oriented based on AOP, which decouples specific business from transaction processing. The code is very intrusive, so declarative transactions are used more often in actual development.

There are also two ways to implement declarative transactions, one is based on the xml configuration file method of TX and AOP, and the other is based on the @Transactional annotation, such as

@Transactional 
@GetMapping("/addCity") 
public String add()  {
    
         
	int result = cityMapper.insert(cityInfo); 
}

2. Introduction to @Transactional annotation

1. Where can the @Transactional annotation be used?

@Transactional can be used on interfaces, classes, and class methods.
Acts on a class: When the @Transactional annotation is placed on a class, it means that all public methods of the class are configured with the same transaction attribute information.
Acts on methods: When the class is configured with @Transactional and the method is also configured with @Transactional, the transaction of the method will overwrite 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 CGLib dynamic proxy, the @Transactional annotation will become invalid

2. Annotation attribute @Transactional

Attributes type describe
value String Optional qualification descriptor specifying the transaction manager to use
propagation enum:Propagation Transaction propagation behavior, the default value is Propagation.REQUIRED
isolation enum: Isolation The isolation level of the transaction, the default value is Isolation.DEFAULT
readOnly boolean Read-write or read-only transaction, the default value is false read-write
timeout int (in seconds granularity) The number of seconds for transaction timeout. The default value is -1, which means it will never timeout.
rollbackFor Class object array An array of exception classes that cause transaction rollback. The default is RuntimeException.
noRollbackFor Array of Class objects, must inherit from Throwable An array of exception classes that will not cause the transaction to be rolled back. By default, it will not be rolled back if it encounters a non-RuntimeException.
rollbackForClassName Array of class names, must inherit from Throwable Array of exception class names that caused transaction rollback
noRollbackForClassName Array of class names, must inherit from Throwable Array of exception class names that will not cause transaction rollback

3. Transmission behavior of affairs

用法:@Transactional(propagation=Propagation.REQUIRED)

  1. REQUIRED (default): If there is currently a transaction, join the current transaction, if not, create a new one.
  2. REQUIRE_NEW: Regardless of whether a transaction exists, a new transaction is created, the original one is suspended, and the old transaction continues to be executed after the new one is executed (the new and old transactions are independent of each other, and the external transaction throws an exception and does not affect the internal transaction) a normal commit)
  3. NESTED: If there is currently a transaction, nest it in the current transaction for execution. If there is no current transaction, create a new transaction.
  4. MANDATORY: It is a mandatory transaction execution. If a transaction does not currently exist, an exception will be thrown (must be executed in an existing transaction, otherwise an exception will be thrown)
  5. NEVER: Execute in a non-transactional manner. If a transaction currently exists, an exception will be thrown (must be executed in a transaction that does not exist, otherwise an exception will be thrown)
  6. SUPPORTS: If there is currently a transaction, join the transaction; if there is currently no transaction, continue running in a non-transactional manner.
  7. NOT_SUPPORT: Run in a non-transactional manner. If a transaction currently exists, the current transaction needs to be suspended.

Note: Spring's transaction propagation level generally does not need to be defined. The default is REQUIRED, unless in the case of nested transactions, you need to focus on it.

Propagation attributes configured as: NEVER, SUPPORTS, NOT_SUPPORT will cause transaction failure

4. Transaction isolation level

用法:@Transactional(isolation = Isolation.DEFAULT)

  1. READ_UNCOMMITTED: Read uncommitted (dirty reading, non-repeatable reading will occur), basically not used
  2. READ_COMMITTED: The read has been committed (non-repeatable reads and phantom reads will occur)
  3. REPEATABLE_READ: repeatable reading (phantom reading will occur)
  4. SERIALIZABLE: serialization

MYSQL: Default is REPEATABLE_READ
SQLSERVER: Default is READ_COMMITTED

5. Transaction timeout settings

Example: @Transactional(timeout=30)
If the transaction is not completed after 30 seconds, the transaction will be automatically rolled back.

3. Annotate failure scenarios

1. @Transactional is applied to the method modified by 非public

If Transactional annotation is applied to a non-public modified method, Transactional will be invalid.

Transaction execution process
The reason why fails is because when using Spring AOP proxy, as shown in the figure above, the 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 The
computeTransactionAttribute method of AbstractFallbackTransactionAttributeSource will be called indirectly 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.

Note: @Transactional annotation is used on protected and private modified methods. Although the transaction is invalid, no error will be reported. This is an easy mistake.

2. @Transactional annotation attributepropagation设置错误

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

SUPPORTS: If there is currently a transaction, join the transaction; if there is currently no transaction, continue running in a non-transactional manner.
NOT_SUPPORTED: Run in non-transactional mode. If a transaction currently exists, the current transaction will be suspended.
NEVER: Run in non-transactional mode and throw an exception if a transaction currently exists.

3. @Transactional annotation attributerollbackFor 设置错误

rollbackFor can specify the exception type that can trigger transaction rollback. Spring rolls back the transaction only when an unchecked exception (an exception inherited from RuntimeException) or Error is thrown by default; other exceptions will not trigger the rollback of the transaction. If other types of exceptions are thrown in the transaction, but you expect Spring to rollback the transaction, you need to specify the rollbackFor attribute.
abnormal
// I hope that customized exceptions can be rolled back

@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. 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);
}

4, 同一个类中方法调用, causing @Transactional to fail

During development, it is inevitable to call methods in the same class. For example, there is a class Test, which has a method A. A then calls method B of this class (regardless of whether method B is modified with public or private), but method A does not. Declaration annotation transaction, and B method has. After calling method A externally, the transaction of method B will not take effect. This is also a place where mistakes are often made.

So why does this happen? In fact, this is 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("/test")
    private Integer A() throws Exception {
    
    
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
        /**
         * B 插入字段为 3的数据
         */
        this.insertB();
        /**
         * A 插入字段为 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, 异常被 catch“吃了”, causing @Transactional to fail

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

	@Transactional
    private Integer A() throws Exception {
    
    
        int insert = 0;
        try {
    
    
            CityInfoDict cityInfoDict = new CityInfoDict();
            cityInfoDict.setCityName("2");
            cityInfoDict.setParentCityId(2);
            /**
             * A 插入字段为 2的数据
             */
            insert = cityInfoDictMapper.insert(cityInfoDict);
            /**
             * B 插入字段为 3的数据
             */
            b.insertB();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

If an exception is thrown internally in method B, and method A tries to catch the exception in method B, 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

Because when an exception is thrown in ServiceB, ServiceB identifies that the current transaction needs to be rolled back. But in ServiceA, because you manually caught this exception and processed it, ServiceA believes that the current transaction should be committed normally. At this point, there is an inconsistency, which is why the previous UnexpectedRollbackException exception was thrown.

Spring transactions start before calling the business method. Commit or rollback is executed only after the business method is executed. Whether the transaction is executed depends on whether a runtime exception is thrown. If a runtime exception is thrown and not caught in your business method, the transaction will be rolled back.

Generally, there is no need to catch exceptions in business methods. If you must catch, throw new RuntimeException() must be thrown, or the exception type @Transactional(rollbackFor=Exception.class) must be specified in the annotation. Otherwise, the transaction will be invalid and the data will be committed. The data is inconsistent, so sometimes try catch will be superfluous.

6, 多线程调用, causing @Transactional to fail

Spring transactions are implemented through database connections. A map is saved in the current thread, the key is the data source, and the value is the database connection.
Source code: private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
What we are talking about The same transaction actually refers to the same database connection. Only with the same database connection can it be submitted and rolled back at the same time. If they are in different threads, the database connections obtained must be different, so they are different transactions. For example, if multiple threads are used in method A to call method B, if method B reports an error, the method A transaction will not be rolled back.

7、数据库引擎不支持事务

The probability of this happening is not high. The key is whether the transaction can take effect and whether the database engine supports transactions. The commonly used MySQL database uses the innodb engine that supports transactions by default. Once the database engine is switched to myisam that does not support transactions, the transactions will fundamentally fail.

Recommended article:
Detailed explanation of isolation level
Detailed explanation of propagation behavior

Reference link:
spring transaction annotation
Introduction to Spring transaction annotation

Guess you like

Origin blog.csdn.net/qq_42547733/article/details/128641848