Eight scenarios of Spring transaction failure

What scenarios do you use Spring's @ Transactional annotation to control transactions that do not take effect?

I don't know if my friends have such an experience. When they are happy to write business code, suddenly the transaction in a method seems to fail. Then, when debug ging tracks the code, it is found that after the execution of the first step of the insert or update statement, there is no changed or saved new data in the database immediately.

So I once suspected that the spring transaction failed. Then this article will summarize several common scenarios that give you the illusion of "spring transaction failure", and then apply the remedy to the case.

Let's GO!!!

Based on the problems encountered in my experience, there are probably the following scenarios:

  • Whether the database engine supports transactions (the MyIsam engine of Mysql does not support transactions);

  • Whether the class where the annotation is located is loaded as a Bean (whether it is managed by spring);

  • Whether the method in which the annotation is located is public modified;

  • Whether there is a problem of self calling;

  • Whether the transaction manager is loaded by the data source used;

  • @Whether the extension configuration of Transactional is correct.

Let's expand and analyze each scenario:

The database engine does not support transactions

Take MySQL as an example. Its MyISAM engine does not support transaction operations. InnoDB is the engine that supports transactions. In general, InnoDB is used to support transactions.

According to the official MySQL documentation:

https://dev.mysql.com/doc/refman/5.5/en/storage-engine-setting.html

From MySQL 5.5.0 The default storage engine from 5 is InnoDB, and the previous default is MyISAM, so it should be noted that the underlying engine does not support transactions, no matter what happens, it is useless.

Not managed by Spring

As shown in the following example:

// @Service  
public class OrderServiceImpl implements OrderService {  
   
    @Transactional  
    public void updateOrder(Order order) {  
        // update order  
    }  
   
}  

If the @ Service annotation is commented out at this time, this class will not be loaded into a Bean, and this class will not be managed by Spring, and the transaction will naturally become invalid.

Method is not public

The following are from the official Spring documentation:

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

The general meaning is that @ Transactional can only be used on public methods, otherwise the transaction will not fail. If it is used on non-public methods, you can turn on AspectJ proxy mode.

Self call problem

Let's look at two examples:

//Example 1
   
@Service  
public class OrderServiceImpl implements OrderService {  
   
    public void update(Order order) {  
        updateOrder(order);  
    }  
   
    @Transactional  
    public void updateOrder(Order order) {  
        // update order  
    }  
   
}  
//Example 2
   
@Service  
public class OrderServiceImpl implements OrderService {  
   
    @Transactional  
    public void update(Order order) {  
        updateOrder(order);  
    }  
   
    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void updateOrder(Order order) {  
        // update order  
    }  
   
}  
  • In example 1, there is no @ Transactional annotation on the update method. Call the updateOrder method with @ Transactional annotation. Does the transaction on the updateOrder method work?

  • In example 2, there is no @ Transactional annotation on the update method. Call the updateOrder method with @ Transactional annotation. Does the transaction on the updateOrder method work?

The answer to these two examples is: neither works!

Because they call themselves, they call their own methods of the class without passing through the Spring proxy class. By default, transactions will take effect only when they are called externally. This is also an old classic problem.

One of the solutions to this problem is to inject yourself into your class and call another method with the injected object. This is not very elegant. Another feasible solution can be referred to< How does Spring open another transaction in one transaction? >This article.

The data source does not have a transaction manager configured

As shown in the following code, if the current data source is not configured with a transaction manager, it is also useless!

@Bean  
public PlatformTransactionManager transactionManager(DataSource dataSource) {  
    return new DataSourceTransactionManager(dataSource);  
}  

@Transactional's extended configuration does not support transactions

Propagation.NOT_SUPPORTED: indicates that it does not run as a transaction. If there is a transaction, it will be suspended. This means that running in transaction mode is not supported, so even if the transaction takes effect, it is useless!

@Service  
public class OrderServiceImpl implements OrderService {  
   
    @Transactional  
    public void update(Order order) {  
        updateOrder(order);  
    }  
   
    @Transactional(propagation = Propagation.NOT_SUPPORTED)  
    public void updateOrder(Order order) {  
        // update order  
    }  
   
}  

Abnormal was eaten

This is also a more frequent scenario: eat exceptions and then don't throw them out, and the transaction won't roll back!

@Service  
public class OrderServiceImpl implements OrderService {  
   
    @Transactional  
    public void updateOrder(Order order) {  
        try {  
            // update order  
        } catch {  
   
        }  
    }  
   
}  

Exception type error

Follow the example above and throw another exception

@Service  
public class OrderServiceImpl implements OrderService {  
   
    @Transactional  
    public void updateOrder(Order order) {  
        try {  
            // update order  
        } catch {  
            throw new Exception("Update error");  
        }  
    }  
   
}  

In this way, the transaction will not take effect, because the default rollback is RuntimeException. If you want to trigger the rollback of other exceptions, you need to configure it on the annotation, such as:

@Transactional(rollbackFor = Exception.class)  

This configuration is limited to the Throwable exception class and its subclasses.

summary

This paper summarizes eight scenarios of transaction failure. In fact, the most common scenarios are self call, exception being eaten and wrong exception throwing type. As mentioned at the beginning of the article, this paper does not necessarily summarize the whole, but only summarizes the common scenarios of transaction failure. If you know other scenarios, you are welcome to leave a message to share.

Keywords: Java MySQL Spring Framework

Added by axon on Fri, 31 Dec 2021 09:47:36 +0200