Why @Transactional May Not Work: Three Common Failure Scenarios and Their Spring Internals
The article analyzes three typical situations—non‑public methods, internal self‑calls, and caught exceptions—that cause Spring's @Transactional annotation to be ineffective, explains the underlying proxy and AOP mechanisms, and provides Java code samples and test cases to illustrate each case.
This article explains why the @Transactional annotation sometimes fails to start a transaction in Spring applications and presents three typical failure scenarios.
Scenario 1 – Non‑public method: When a method annotated with @Transactional is not declared public, Spring’s proxy mechanism ignores it because
AbstractFallbackTransactionAttributeSource#computeTransactionAttributereturns null for non‑public methods. The article shows a class with a package‑private method and a test that demonstrates the transaction is not opened.
@Component
public class TestServiceImpl {
@Resource
TestMapper testMapper;
@Transactional
void insertTestWrongModifier() { // not public
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
}Scenario 2 – Internal self‑call: Calling a @Transactional method from another method of the same class bypasses the proxy (the call goes through this), so the transaction is never created. The article provides a service that invokes its own transactional method and a JUnit test that confirms the rollback does not occur.
@Component
public class InvokcationService {
@Resource
private TestServiceImpl testService;
public void invokeInsertTestWrongModifier() {
// direct self‑call, no proxy
testService.insertTestWrongModifier();
}
}Scenario 3 – Swallowing the exception: If a transactional method catches the exception and does not re‑throw it, Spring’s TransactionAspectSupport never sees the error, so the transaction is committed instead of rolled back. The article includes a method that catches Exception and a corresponding test.
@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");
}
}The underlying Spring logic is then examined: during bean creation Spring scans for methods with @Transactional using BeanFactoryTransactionAttributeSourceAdvisor and AopUtils#canApply. Only public methods produce a non‑null TransactionAttribute, which leads to proxy creation. When a method is invoked, TransactionAspectSupport#invokeWithinTransaction obtains the attribute, starts a transaction, executes the method via reflection, and either commits or rolls back based on whether an exception propagates.
In summary, the three reasons for @Transactional ineffectiveness are non‑public visibility, internal method calls that skip the proxy, and catching exceptions inside the transactional method. Understanding Spring’s proxy‑based AOP and ensuring methods are public, called through a Spring bean, and let exceptions propagate will keep transaction management working correctly.
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.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.
