@Transcational transaction annotation failure scenario analysis

[Preface] This note is mainly used to record which scenarios will cause the transaction to fail when the @Transcational annotation is used, and clear pits and mines.

Scenario 1. Transaction management is not enabled

In a Spring application, to use the @Transactional annotation to manage transactions, you must add the @EnableTransactionManagement annotation to the startup class. The function of this annotation is to open Spring's transaction manager and manage the classes that have been annotated with @Transactional. If the @EnableTransactionManagement annotation is not added, the @Transactional annotation will have no effect and cannot manage transactions.

Therefore, if you encounter the problem that the @Transactional annotation is invalid at work, you can check whether the @EnableTransactionManagement annotation has been added to the startup class. If not added, you can add this annotation on the startup class to start the transaction manager.

@SpringBootApplication
@EnableTransactionManagement // 开启事务管理器
public class MyApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(MyApplication.class, args);
    }
}

When using the SpringBoot framework, it automatically enables transaction management for you. The startup class of the SpringBoot application will be annotated with @SpringBootApplication, and the @SpringBootApplication annotation is actually a combination of the @EnableAutoConfiguration annotation and other annotations.

insert image description here

In the @EnableAutoConfiguration annotation, you can see the introduction of the AutoConfigurationImportSelector.class selector.
insert image description here

Check the source code of AutoConfigurationImportSelector directly, and call the getAutoConfigurationEntry method in the selectImports method.
insert image description here

Enter the method, you can see the method of getCandidateConfigurations
insert image description here

Entering this method, you can know that this method will read the spring.factories file, we can find org.springframework.boot:spring-boot-autoconfigure from the project's External Libraries, open the META-INF folder, and find it in the spring directory Annotated files:

insert image description here

You can see the TransactionAutoConfiguration class related to transaction in the file:
insert image description here

Find this class, as you can see from the relevant description, this class is related to the automatic assembly transaction:
insert image description here

In this class, I saw the enabling annotation of @EnableTransactionManagement:
insert image description here

Scenario 2. The database engine does not support transactions

The probability of this situation is not high. Whether the transaction can take effect or whether the database engine supports transactions is the key. 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 are fundamentally invalid.

Scenario 3. @Transactional is applied to non-public modified methods

When @Transactionalthe annotation is applied to a non- publicdecorated method, it will have no effect. This is because when Spring AOP is proxied, TransactionInterceptor(transaction interceptor) will intercept before and after the execution of the target method. During this process, interceptthe method will indirectly call the methodAbstractFallbackTransactionAttributeSource of to obtain the transaction configuration information of the annotation.computeTransactionAttributeTransactional

In computeTransactionAttributethe method , the modifier of the target method is checked public. If not public, the attribute configuration information @Transactionalof .

/**
	 * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
	 * {@link #getTransactionAttribute} is effectively a caching decorator for this method.
	 * <p>As of 4.1.8, this method can be overridden.
	 * @since 4.1.8
	 * @see #getTransactionAttribute
	 */
	@Nullable
	protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    
    
		// Don't allow non-public methods, as configured.
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    
    
			return null;
		}
		...
	}

Note: In this case, although the transaction is invalid, no error will be reported. This is a place that needs to avoid pitfalls.

Scenario 4: try/catch handles exceptions in the program

We enter the @Transactional annotation to view its details, and we can see the following requirements in the rollback attribute:

By default, rollback is only triggered when an Error or RuntimeException occurs. Therefore, if the exception is caught but not thrown, the transaction rollback will not take effect.

insert image description here

Business methods that require transactions generally do not need to catch exceptions. If you must catch, you must throw throw new RuntimeException(), or specify the throwing exception type @Transactional(rollbackFor=Exception.class) in the annotation, otherwise the transaction will become invalid.

Scenario 5. @Transactional annotation attribute rollbackFor is set incorrectly

rollbackFor can specify the exception type that can trigger transaction rollback. By default, Spring throws an unchecked unchecked exception (inherited from RuntimeException) or Error to roll back the transaction; other exceptions will not trigger the rollback transaction. If you expect Spring to roll back the transaction when another type of exception or a subclass of this exception is thrown in the transaction, you need to specify the rollbackFor attribute.

Examples are as follows:

@Transactional(rollbackFor = Exception.class)
Scenario 6. Method calls in the same class cause @Transactional to fail (easy to commit)

During development, it is unavoidable to call methods in the same class. For example, there is a class Test, a method A of it, and A calls method B of this class, but method A does not declare an annotation transaction, 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.
Reason: In fact, this is caused by the use of Spring AOP proxy, because only when the transaction method is called by code other than the current class, it will be managed by the proxy object generated by Spring.

Because Spring will dynamically generate a subclass (that is, the proxy class proxy) for the annotated method when scanning the bean, and then when the annotated method is called, it is actually called by the proxy, and the proxy will start before the call transaction. However, if this annotated method is called by other methods in the same class, then the method is not called through the proxy, but directly through the original bean, so the transaction will not be started, that is, the @Transactional annotation invalid.

Scenario 7. Using @Transactional annotations in non-Spring container-managed classes

When using the @Transactional annotation in a class not managed by the Spring container, Spring AOP cannot intercept the methods in the class, so transaction management cannot take effect.

Scenario 8: DDL statements cannot be rolled back

In MySQL, DDL statements are non-transactional. This means that DDL statements cannot be rolled back. If a DDL statement is executed in a transaction, it will cause the transaction to be implicitly committed, so that the previously executed DML operations in the transaction cannot be rolled back.

For example, for table clearing operations, when truncate is used to clear the table, the transaction cannot be rolled back. When it is rewritten as a delete command, the data can be rolled back normally:

# DDL 无法回滚
truncate tabelA;
# DML 可以回滚
delete from tabeA;

In MySQL 8.0, rollback of DDL statements within a transaction is supported. In versions prior to MySQL 8.0, if a DDL statement was executed within a transaction, it would cause the transaction to commit implicitly. If rollback is executed in the transaction, only the DML statements in the transaction will be rolled back, but the DDL statements cannot be rolled back.

Therefore, in order to avoid DDL statements from breaking the transaction, DDL statements should be avoided in the transaction. If you need to use DDL statements, you should execute DDL statements and DML statements separately, or use MySQL 8.0 and above.

Guess you like

Origin blog.csdn.net/zhzh980/article/details/129887764