Xiao Ming: Pretty boy, I have met something very strange recently.

Pretty boy: Oh? Tell me.

Xiaoming: Last time I read your article “That’s it? An article to make you understand Spring Transactions”, I had a detailed understanding of transactions. However, I still ran into problems in the project.

Pretty boy: Then today I will give you a summary of the scenarios in which the transaction will fail.

The database engine does not support transactions

Mysql 5.5.5 uses InnoDB as its default storage engine, while Mysql 5.5.5 uses MyISAM as its default storage engine. InnoDB, so make sure you use a database that does not support transactions.

2. Not managed by Spring

The class of the transaction method is not injected into the Spring container, as follows:

public class OrderServiceImpl implements OrderService {    
    @Autowired    
    AccountMapper accountMapper;    
    @Autowired   
    ProductMapper productMapper; 
    
    @Transactional    
    @Override    
    public void placeOrder(a) {        
        // Omit a bunch of logic here
        
        // Modify user balance and merchandise inventoryaccountMapper.update(); productMapper.update(); }}Copy the code

If this class is not annotated with @service, the transaction will not take effect.

3. Not a public method

As the official documentation makes clear, the @Transactional annotation can only be used with public methods. If you want to use it with non-public methods, you can turn on the AspectJ proxy mode.

4. The exception is caught

Take this example:

@Service
public class OrderServiceImpl implements OrderService {    
    @Autowired    
    AccountMapper accountMapper;    
    @Autowired    
    ProductMapper productMapper;    
    
    @Transactional    
    @Override    
    public void placeOrder(a) {        
        try{            
            // Omit a bunch of logic here
            
            // Modify user balance and merchandise inventory
            accountMapper.update();            
            productMapper.update();        
        } catch (Exception e) {  
            
        }     
    }
}
Copy the code

When an exception occurs in this method, the transaction will fail because the exception is caught and not thrown. How to resolve this situation? Don’t worry, look down

@Service
public class OrderServiceImpl implements OrderService {    
    @Autowired    
    AccountMapper accountMapper;    
    @Autowired    
    ProductMapper productMapper; 
    
    @Transactional    
    @Override    
    public void placeOrder(a) {       
        try{            
            // Omit a bunch of logic here
            
            // Modify user balance and merchandise inventory
            accountMapper.update();            
            productMapper.update();        
        } catch (Exception e) {            
            // Manually roll backTransactionAspectSupport.crrentTransactionStatus().setRollbackOnly(); }}}Copy the code

Can be achieved by

TransactionAspectSupport.crrentTransactionStatus().setRollbackOnly();

Manually roll back.

5. The exception type is incorrect

The @Transactional annotation only rolls back exceptions of type RuntimeException by default, so it is recommended that you change it to Exception when you use it

@Transactional(rollbackFor = Exception.class)

6. Call transaction methods internally

This is probably the most common scenario where a transaction fails, and it’s the one I’m going to focus on.

Some relatively complex business logic operations, such as methods of order of the previous example, often before the write operation will have a heap of logic, if all operations in a method, and combined with the transaction, so will likely because the transaction execution time is too long, cause the transaction timeout, even if no timeout orders will also affect the performance of the interface. At this point, you can isolate the write operations and add transactions only to the write operations, so the pressure is much less.

Consider the following example:

@Service
public class OrderServiceImpl implements OrderService {    
    @Autowired    
    AccountMapper accountMapper;    
    @Autowired    
    ProductMapper productMapper;    
    
    @Override    
    public void placeOrder(a) {        
        // Omit a bunch of logic here
        this.updateByTransactional();    
    }        
    
    @Transactional    
    public void updateByTransactional(a) {        
        // Modify user balance and merchandise inventoryaccountMapper.update(); productMapper.update(); }}Copy the code

Because an internal call occurs and does not go through Spring’s proxy, the transaction will not take effect, as noted in the official documentation:

So what do you do in this case?

Plan 1: Make an external call instead

Internal calls don’t work, so I’ll make external calls instead

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    OrderTransactionService orderTransactionService;
    
    @Override
    public void placeOrder(a) {
        // Omit a bunch of logic hereorderTransactionService.updateByTransactional(); }}@Service
public class OrderTransactionService {
    @Autowired
    AccountMapper accountMapper;
    @Autowired
    ProductMapper productMapper;
    
    @Transactional
    public void updateByTransactional(a) {
        // Modify user balance and merchandise inventoryaccountMapper.update(); productMapper.update(); }}Copy the code

That’s the easy way to understand it

Option two: Use programmatic transactions

Since declarative transactions are problematic, can I switch to programmatic transactions?

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    AccountMapper accountMapper;
    @Autowired
    ProductMapper productMapper;
    @Autowired
    TransactionTemplate transactionTemplate;

    @Override
    public void placeOrder(a) {
        // Omit a bunch of logic here
        
        / / TransactionCallbackWithoutResult no return parameter
        // TransactionCallback has a return parameter
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    this.updateByTransactional();
                } catch (Exception e) {
                    log.error("Order failed", e); transactionStatus.setRollbackOnly(); }}}); }public void updateByTransactional(a) {
        // Modify user balance and merchandise inventoryaccountMapper.update(); productMapper.update(); }}Copy the code

Don’t care if it’s black or white, it’s a good cat that catches mice

Plan 3: Call it back through external methods

This is a method THAT I saw provided by netizens, and want to use annotations, and want to call, so you can refer to the way of programmatic transactions to achieve.

@Component
public class TransactionComponent {
    public interface Callback<T>{
        T run(a) throws Exception;
    }

    public interface CallbackWithOutResult {
        void run(a) throws Exception;
    }

    // Take a return argument
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Nullable
    public <T> T doTransactional(Callback<T> callback) throws Exception {
        return callback.run();
    }

    // No return argument
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Nullable
    public void doTransactionalWithOutResult(CallbackWithOutResult callbackWithOutResult) throws Exception { callbackWithOutResult.run(); }}Copy the code

This will resolve the invalidation problem by calling internal methods through the TransactionComponent.

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    AccountMapper accountMapper;
    @Autowired
    ProductMapper productMapper;
    @Autowired
    TransactionComponent transactionComponent;

    @Override
    public void placeOrder(a) {
        // Omit a bunch of logic here
        
        transactionComponent.doTransactionalWithOutResult(() -> this.updateByTransactional());
    }
    
    public void updateByTransactional(a) {
        // Modify user balance and merchandise inventoryaccountMapper.update(); productMapper.update(); }}Copy the code

conclusion

This article summarizes several common transaction failure scenarios and some solutions, which may or may not be exhaustive. What other scenes have you met that I haven’t mentioned? Welcome to share and correct any shortcomings.

END

Phase to recommend

Is this it? An article will help you read Spring transactions

SpringBoot+Redis implements message subscription publishing

The most detailed graphic analysis of various Locks in Java

Common code refactoring techniques that you can use

Graphic description of 23 design modes