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.
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
saveBatchinternally 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
saveBatchmethod – not possible because it is hard‑coded in the library.
Change the transaction propagation of
saveBatchto
REQUIRES_NEWor
NESTED– also not configurable in the library.
Since the library cannot be altered, the practical fix is to bypass
saveBatchand 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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.