SoFunction
Updated on 2025-05-13

9 major scenarios and solutions for Spring transaction failure

Preface

In daily development, we often useSpringtransaction. Recently, a friend went for an interview and was asked about an interview question: Under what circumstances will Spring affairs fail?

Today, I will talk to youSpring9 scenarios of transaction failure.

1. Throw checked exceptions

For example, your transaction control code is as follows:

@Transactional
public void transactionTest() throws IOException {
    User user = new User();
    (user);
    throw new IOException();
}

If not specified@Transactional, Spring will only encounter runtime exceptions by defaultRuntimeExceptionor roll back when errors are performed, and check for exceptions such asIOExceptionRollback will not be triggered.

public boolean rollbackOn(Throwable ex) {
    return (ex instanceof RuntimeException || ex instanceof Error);
}

Solution:

Once you know the reason, the solution is also very simple. ConfigurationrollbackForProperties, for example:@Transactional(rollbackFor = )

@Transactional(rollbackFor = )
public void transactionTest() throws IOException {
    User user = new User();
    (user);
    throw new IOException();
}

2. The business method itself catches and handles exceptions

@Transactional(rollbackFor = )
public void transactionTest() {
    try {
        User user = new User();
        (user);
        int i = 1 / 0;
    } catch (Exception e) {
        ();
    }
}

In this scenario, the reason for transaction failure is also simple. Whether Spring rolls back transactions depends on whether you throw an exception. If you catch the exception yourself, Spring will not be able to handle the transaction.

After reading the above code, you may think that it is impossible for you to make such a low-level mistake with such a simple problem. But I want to tell you that almost half of the people around me have been bothered by this.

When writing business code, the code can be more complex and there are many nested methods. If you don't pay attention, this problem can easily be triggered. To give a simple example, suppose you have an audit function. After each method is executed, save the audit results to the database. Then the code might be written like this:

@Service
public class TransactionService {
    @Transactional(rollbackFor = )
    public void transactionTest() throws IOException {
        User user = new User();
        (user);
        throw new IOException();
    }
}

The following section will act onTransactionService

@Component
publicclass AuditAspect {
    @Autowired
    private AuditService auditService;

    @Around(value = "execution (* .*.*(..))")
    public Object around(ProceedingJoinPoint pjp) {
        try {
            Audit audit = new Audit();
            Signature signature = ();
            MethodSignature methodSignature = (MethodSignature) signature;
            String[] strings = ();
            (());
            (strings);
            Object proceed = ();
            (true);
            return proceed;
        } catch (Throwable e) {
            ("{}", e);
            (false);
        }
        (audit);
        returnnull;
    }
}

In the above example, if the program executes abnormally, the transaction will also fail. The reason isSpringThe transaction section has the lowest priority. If the exception is caught by a section, Spring naturally cannot handle the transaction correctly because the transaction manager cannot catch the exception.

Solution:

Just removetry-catch. Although we know that business code cannot catch exceptions by itself when handling transactions, it is easy to accidentally make mistakes as long as the code becomes complicated.

3. Method calls in the same class

@Service
publicclass DefaultTransactionService implements Service {

    public void saveUser() throws Exception {
        // do something
        doInsert();
    }

    @Transactional(rollbackFor = )
    public void doInsert() throws IOException {
        User user = new User();
        (user);
        thrownew IOException();
    }
}

This is also a mistake-prone scenario. The reason for transaction failure is also very simple. Because Spring's transaction management function is implemented through dynamic proxy, and Spring uses JDK dynamic proxy by default, JDK dynamic proxy is implemented through interfaces and calls the target class through reflection. Simple understanding, insaveUser()In the method, call()hour,thisIt is a real object, so it will be executed directlydoInsertbusiness logic, not proxy logic, resulting in transaction failure.

Solution:

Plan 1: DirectlysaveUserAdd on the method@Transactionalannotation.

Solution 2: These two methods can be split into different classes.

Solution 3: Instead of using annotations to implement transactions, programmatic transactions are used to wrap the code blocks that need to be opened. For example:()

public void doInsert() throws IOException {
    (() -> {
        User user = new User();
        (user);
        throw new IOException();
    });
}

4. The method uses final or static keywords

If Spring is implemented using Cglib proxy (when your proxy class does not implement the interface), your business method happens to use itfinalorstaticKeywords, then transaction control will also be invalid. Because Cglib uses bytecode enhancement technology to generate subclasses of the proxy class and override the proxy class method to implement the proxy. If the proxy method is usedfinalorstaticKeywords, subclasses cannot rewrite the proxy method.

If Spring is implemented using JDK dynamic proxy, which is implemented based on an interface, thenfinalandstaticThe modification method cannot be proxyed either.

In short, if the method does not even have a proxy, then transaction rollback will definitely not be implemented.

Solution:

Try to remove the methodfinalorstaticKeywords.

5. The method is not public

If the method is notpublic, Spring transactions will also fail because the source code of Spring transaction managementAbstractFallbackTransactionAttributeSourcemiddle,computeTransactionAttribute()The method will determine whether the target method ispublic. If notpublic, then returnnull

// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !(())) {
    return null;
}

Solution:

Change the access level of the current method topublic

6. Improper use of the propagation mechanism

SpringThe transaction propagation mechanism refers to the strategy of how transactions should be propagated when multiple transaction methods are called to each other.SpringSeven transaction propagation mechanisms are provided:REQUIREDSUPPORTSMANDATORYREQUIRES_NEWNOT_SUPPORTEDNEVERNESTED. If you don't understand the principles of these propagation strategies, it is easy to cause transaction failure.

@Service
publicclass TransactionsService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AddressMapper addressMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = )
    public void doInsert(User user, Address address) throws Exception {
        // do something
        (user);
        saveAddress(address);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveAddress(Address address) {
        // do something
        (address);
    }
}

In the above example, if the user insertion fails, it will not causesaveAddress()Rollback, because the propagation mechanism used here isREQUIRES_NEWREQUIRES_NEWThe principle is that if there is no transaction in the current method, a new transaction is created. If the current method already has a transaction, the current transaction is suspended and a new transaction is created. The parent transaction will wait until the current transaction is completed before committing. If an exception occurs in the parent transaction, the commit of the child transaction will not be affected.

Solution:

Change the transaction propagation policy to default valueREQUIREDREQUIREDThe principle is that if there is currently a transaction, it will be added to the transaction. If there is no transaction, a new transaction is created. The parent transaction and the called transaction are in the same transaction. Even if the called transaction catches the exception, the entire transaction will still roll back.

7. Not managed by Spring

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

If this is the case@ServiceIf the annotation is commented out, the class will not be loaded as a bean by Spring, and the class will not be managed by Spring, and the transaction will naturally become invalid.

Solution:

Make sure that each transaction annotation is usedServiceAll managed by Spring.

8. Multithreaded call

@Service
publicclass UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
        (userModel);
        new Thread(() -> {
            try {
                test();
            } catch (Exception e) {
                ();
            }
        }).start();
    }
}

@Service
publicclass RoleService {

    @Transactional
    public void doOtherThing() {
        try {
            int i = 1 / 0;
            ("save role table data");
        } catch (Exception e) {
            thrownew RuntimeException();
        }
    }
}

We can see that in transaction methodsaddIn, the transaction method is calleddoOtherThing,butdoOtherThingIt is called in another thread.

This causes the two methods to be not in the same thread and the database connections obtained are also different, so they are two different transactions. IfdoOtherThingThe exception is thrown in the method.addThe method is impossible to roll back.

What we mean by the same transaction actually refers to the same database connection. Only under the same database connection can you commit and rollback simultaneously. If in different threads, the obtained database connections must be different, so they are different transactions.

Solution:

This is a bit like distributed transactions. Try to ensure that it is handled in the same transaction.

9. No transaction is enabled

If Spring's transaction manager is not configured in the project, Spring's transactions will not take effect even if Spring's transaction management function is used. For example, if you are a Spring Boot project and you do not configure the following code in your Spring Boot project:

@EnableTransactionManagement

Solution:

Make sure the transaction manager is configured correctly in the project.

Summarize

This article briefly explains the implementation principle of Spring transactions and lists 9 scenarios in which Spring transactions fail. I believe many friends may have encountered these problems. The article also explains the reasons for the failure in detail. I hope everyone has a new understanding of Spring transactions.

This is the end of this article about 9 major scenarios and solutions for Spring transaction failure. For more related Spring transaction failure content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!