Why @Transactional May Not Work: Common Failure Scenarios and Their Underlying Mechanisms
This article explains three typical situations where Spring's @Transactional annotation fails—non‑public methods, internal method calls, and caught exceptions—detailing the proxy‑based AOP mechanism, source code examples, and how to diagnose and resolve each issue.
The article introduces three common cases in which the @Transactional annotation becomes ineffective in Spring applications.
Scenario 1: Non‑public Method Modifier
When a method annotated with @Transactional is declared with a visibility other than public, Spring's dynamic proxy does not apply transaction advice, so the transaction is never started. The source code shows a class TestServiceImpl with a default‑access method insertTestWrongModifier that fails to roll back.
/**
* @author zhoujy
*/
@Component
public class TestServiceImpl {
@Resource
TestMapper testMapper;
@Transactional
void insertTestWrongModifier() {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
}A separate InvokcationService calls this method, demonstrating that the transaction is not opened and the insert operation is not rolled back.
Scenario 2: Internal Method Invocation
Calling a @Transactional method from another method within the same class bypasses the proxy, because the call is made on this rather than the proxied bean. The article provides an example where testInnerInvoke invokes insertTestInnerInvoke directly, resulting in no transaction.
public void testInnerInvoke() {
// class internal call, no proxy
insertTestInnerInvoke();
}To make the transaction work, the internal call must go through the proxy, e.g., by injecting the bean itself and calling testServiceImpl.insertTestInnerInvoke().
Scenario 3: Swallowing Exceptions
If a transactional method catches an exception and does not re‑throw it, Spring cannot detect the failure and therefore will not roll back the transaction. The provided code catches Exception and only prints a message, so the subsequent insert is committed.
@Transactional
public void insertTestCatchException() {
try {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
} catch (Exception e) {
System.out.println("i catch exception");
}
}Underlying Mechanism
The article explains that Spring creates a proxy for beans that contain at least one public method annotated with @Transactional. During proxy creation, AopUtils.canApply scans methods for the annotation; non‑public methods are ignored, leading to no proxy or no advice for those methods.
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
// ... iterate over methods and check methodMatcher.matches(...)
}When the proxy intercepts a method call, it retrieves the transaction attribute via
AbstractFallbackTransactionAttributeSource#getTransactionAttribute. If the attribute is null (e.g., because the method is non‑public), no transaction is started.
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null; // no transaction attribute
}
// ... other logic
}Finally, the article shows the core transaction execution flow in TransactionAspectSupport#invokeWithinTransaction, highlighting that only uncaught exceptions trigger a rollback.
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
// ... create transaction, proceed, catch Throwable, rollback if needed
}Understanding these mechanisms helps developers diagnose why @Transactional may appear to be ineffective and apply appropriate fixes.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
