@Transactional annotation failure scenario and solution version

Failure scenario

1 The database must first support transactions:

Taking mysql as an example, its MyISAM engine does not support transaction operations, while InnoDB only supports transactions. Starting from mysql5.5, the default engine is InnoDB, and the previous default was MyISAM.

2. The data source does not have a transaction manager configured

3. Not managed by spring

Transaction annotations are added to the service layer. If there is no @Service annotation, this class will not be loaded into a Bean, then this class will not be managed by spring, and the transaction will naturally fail.

4. The method is not public

According to the spring official website, @Transactional can only be used on public methods, otherwise the transaction will not take effect. If it must be used on non-public methods, you can enable AspectJ proxy mode. AspectJ uses load-time weaving to support all pointcuts, so it can support transaction settings for internal methods.

5.@Transactional annotation attribute propagation setting error

If the annotation configuration does not support transactions, Propagation is used. NOT_SUPPORTED: Indicates that it is not easy for the transaction to run. If there is currently a transaction, it will be suspended.

6. Method calls in the same class cause @Transactional to become invalid.

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, it 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("/test")
    private void a(User user) throws Exception {
    
    
        userMapper.insert(user);
        b(user);
    }
 
    @Transactional
    public void b(User user) throws Exception {
    
    
       doSomething();
    }

Replenish:

If method a is also annotated with @Transactional, will the transaction take effect at this time?

   上面的代码只是放开a方法上的@Transactional注解,此时a方法的事务依然是无法生效的。我们看到在事务方法a中,直接调用事务方法b。从前面介绍的内容可知,b方法拥有事务是因为spring aop生成了代理对象,但是这种方法直接调用了this对象的方法,所以a方法不会生成事务。

It can be seen that calling methods in the same class directly internally will cause the transaction to fail.

So the question is, how to solve this scenario?

Method 1: Add a new service method

This method is very simple. You only need to add a new Service method, add the @Transactional annotation to the new Service method, and move the code that requires transaction execution to the new method.

​
​
@Servcie 
public class ServiceA {
    
     
   @Autowired 
   prvate ServiceB serviceB; 
 
   public void save(User user) {
    
     
         queryData1(); 
         queryData2(); 
         serviceB.doSave(user); 
   } 
 } 
 
 @Servcie 
 public class ServiceB {
    
     
 
    @Transactional(rollbackFor=Exception.class) 
    public void doSave(User user) {
    
     
       userMapper.insert(user); 
       b(user); 
    } 
 
 } 
 
​
 
​

Method 2: Inject yourself into the Service class

If you don't want to add a new Service class, injecting yourself into the Service class is also an option.

​
​
​
@Servcie 
public class ServiceA {
    
     
   @Autowired 
   prvate ServiceA serviceA; 
 
   public void save(User user) {
    
     
         queryData1(); 
         queryData2(); 
         serviceA.doSave(user); 
   } 
 
   @Transactional(rollbackFor=Exception.class) 
    public void doSave(User user) {
    
     
       userMapper.insert(user); 
       b(user); 
    } 
 }

This approach will not cause circular dependency problems. In fact, the third-level cache inside spring IOC ensures that there will be no circular dependency problems.

Method 3: Through the AopContext class

Introduce dependencies

<dependency>
   <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
</dependency>

Enable AOP acquisition (add to SpringBoot startup class or configuration class)

@EnableAspectJAutoProxy(exposeProxy = true)

Use AopContext.currentProxy() in the Service class to obtain the proxy object

@Servcie 
public class ServiceA {
    
     
  
   public void save(User user) {
    
     
         queryData1(); 
         queryData2(); 
         ((ServiceA)AopContext.currentProxy()).doSave(user); 
   } 
 
   @Transactional(rollbackFor=Exception.class) 
    public void doSave(User user) {
    
     
       userMapper.insert(user); 
       b(user); 
    } 
 } 

How can the transaction be rolled back if the exception is eaten and then not thrown?

If you want spring transactions to be rolled back normally, you must throw an exception that it can handle. If no exception is thrown, spring considers the program to be normal.

To put it bluntly, even if the developer does not manually catch the exception, if the exception thrown is incorrect, the spring transaction will not be rolled back.

Because spring transactions only roll back RuntimeException (runtime exception) and Error (error) by default, it will not roll back ordinary Exception (non-runtime exception).

7. Catch the exception through try catch

8. Exception type error [@Transactional annotation attribute rollbackFor setting error]

The default rollback is RuntimeException. If you want to trigger other exception rollbacks, you need to configure the annotation.

@Transactional(rollbackFor = Exception.class)

9. Methods are modified with final

Sometimes, a method does not want to be overridden by subclasses, then the method can be defined as final. It is no problem to define ordinary methods in this way, but if the transaction is defined as final, this will cause the transaction to become invalid.

why?

   spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。

   但如果某个方法被final修饰了,那么在它的代理类中,就无法重写该方法,事务也就失效了。

Note: If a method is static, it also cannot become a transaction method through dynamic proxy.

10.Multi-threaded calls

In actual project development, there are still many usage scenarios for multi-threading. Will there be any problems if spring transactions are used in multi-threaded scenarios?

@Slf4j 
@Service 
public class UserService {
    
     
 
    @Autowired 
    private UserMapper userMapper; 
    @Autowired 
    private RoleService roleService; 
 
    @Transactional 
    public void add(UserModel userModel) throws Exception {
    
     
        userMapper.insertUser(userModel); 
        new Thread(() -> {
    
     
            roleService.doOtherThing(); 
        }).start(); 
    } 
} 
 
@Service 
public class RoleService {
    
     
 
    @Transactional 
    public void doOtherThing() {
    
     
        System.out.println("保存role表数据"); 
    } 
} 

From the above example, we can see that the transaction method doOtherThing is called in the transaction method add, but the transaction method doOtherThing is called in another thread.

This will result in the two methods not being in the same thread and obtaining different database connections, resulting in two different transactions. If an exception is thrown in the doOtherThing method, it is impossible to roll back the add method.

If you have read the spring transaction source code, you may know that 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.

The same transaction we are talking about actually refers to the same database connection. Only with the same database connection can we commit and rollback at the same time. If they are in different threads, the database connections obtained must be different, so they are different transactions.

Guess you like

Origin blog.csdn.net/u014212540/article/details/132203122