Backend Development 6 min read

Why MyBatis-Plus saveBatch Triggers Unexpected Transaction Rollback and How to Fix It

This article explains a puzzling Spring Boot transaction rollback caused by MyBatis-Plus's saveBatch method, reproduces the error with nested @Transactional calls, analyzes why the rollback flag is set, and offers a practical workaround by replacing the framework batch operation with a custom mapper implementation.

macrozheng
macrozheng
macrozheng
Why MyBatis-Plus saveBatch Triggers Unexpected Transaction Rollback and How to Fix It

Problem Reproduction

A test reported the error message

Transaction rolled back because it has been marked as rollback-only

, which occurs when an inner transaction fails but the outer transaction still attempts to commit.

The failure originates from nested @Transactional methods: a service method marked with @Transactional calls another service method also marked with @Transactional. When the inner method throws an exception, it marks the whole transaction as rollback‑only; the outer method catches the exception, continues execution, and finally tries to commit, triggering the rollback error.

Code Example

<code>@Override
@Transactional(rollbackFor = Exception.class)
public Boolean xxx(xxxDto dto) {
    list1 = ...;
    try {
        // batch save list1
    } catch (Exception e) {
        if (e instanceof DuplicateKeyException) {
            // filter duplicates and save again
        }
        // other handling
    }
    sendToMQ(xxx);
    list2 = ...;
    try {
        // batch save list2
    } catch (Exception e) {
        if (e instanceof DuplicateKeyException) {
            // filter duplicates and save again
        }
        // other handling
    }
    sendToMQ(xxx);
    return Boolean.TRUE;
}
</code>

The method is intended as a one‑time data‑fix operation, but because MyBatis‑Plus’s

saveBatch

internally adds its own @Transactional annotation, the batch operation forces a new transaction scope, causing the rollback flag to be set even though the outer code catches the exception.

Solution

Remove the @Transactional annotation from MyBatis‑Plus’s

saveBatch

method – not possible because it is hard‑coded in the library.

Change the transaction propagation of

saveBatch

to

REQUIRES_NEW

or

NESTED

– also not configurable in the library.

Since the library cannot be altered, the practical fix is to bypass

saveBatch

and implement a custom batch insert using a MyBatis mapper, thereby avoiding the unwanted transaction behavior.

Conclusion

When using third‑party frameworks, always verify their transaction semantics; hidden @Transactional annotations can introduce subtle bugs, especially in scenarios with duplicate‑key handling and nested service calls.

BackendJavaTransactionSpringBootMyBatis-Plusnested-transaction
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.