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

@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;
}

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.