Skip to main content

🎭 Spring’s Secret: Why @Transactional & Friends Betray You Silently

πŸ’‘ Lesson Learned — Not a Prod Bug, But a Real Pain

No, this wasn’t a production outage.
Nobody screamed at me.
But I sat for 3 hours wondering:

“Why the heck is my @Transactional not rolling back!?” 😡‍πŸ’«
“Why is Redis cache not working?” 🀯

Turned out, the issue was one silent villain:

🧱 Self-invocation

🀷 What Is @Transactional?

If you're new:

@Transactional = Tells Spring to start a DB transaction when a method is called.
It’ll commit if everything’s okay.
It’ll rollback if something fails.

🧠 Think of it like wrapping your code in:

try {
   beginTransaction();
   // your logic
   commit();
} catch(Exception e) {
   rollback();
}

πŸ•΅️ Real-Life Analogy — The Gateway Community 🏘️

Let me tell you about my society — it has a strict watchman at the gate. Here’s how it works:

  • πŸ›‚ Watchman = Spring Proxy
  • 🏠 Your apartment = Your service class
  • πŸšͺ Your room = A method inside that class

πŸƒ Scenario 1: Outsider Visits

Your friend from outside the society tries to visit you.

The watchman stops him at the gate.
Asks for ID, checks the visitor list, logs entry, and then lets him in.

🎯 This is like Spring calling your method from another bean:

someOtherService.callYourMethod(); // ✅ Proxy intercepts
  • πŸš€ Transaction starts
  • 🧊 Cache is checked
  • πŸ’₯ Circuit breakers are applied

🧍‍♂️ Scenario 2: Your Neighbor Walks Into Your Room

But if your next-flat neighbor (same building) enters directly through the balcony, skipping the gate:

The watchman doesn't even know.
No checks. No logging. No nothing.

😱 That’s what happens when a method in the same class calls another annotated method:

public void yourOwnMethod() {
    this.annotatedMethod(); // ❌ No proxy = Spring does nothing
}

🧠 So the rule is simple:

πŸ’¬ “Spring’s watchman (proxy) only watches external visitors.
If you're inviting yourself or your neighbor is hopping over from the balcony...
He doesn't care.” πŸ˜„

🚫 Annotations That Won’t Work With Self-Call

These annotations rely on Spring Proxies.
But if you call another method inside the same class, the proxy is bypassed = annotations don’t work.

⚠️ FULL LIST — Don’t Miss These!

πŸ”– Annotation ⚠️ Why It Breaks in Self-Call πŸ’‘ What It Does
@TransactionalNo transaction startedDB commit/rollback
@CacheableCache not checkedCaches method result
@CachePutCache not updatedUpdates cache
@CacheEvictCache not clearedEvicts cache
@AsyncRuns in same threadRuns in separate thread
@ScheduledWon’t auto-trigger selfRuns on cron/interval
@RetryableWon’t retry methodAuto-retry failed methods
@RateLimiterNo limit appliedRestricts method rate
@CircuitBreakerCircuit not opened/closedHandles failure gracefully
@TimeLimiterNo timeout appliedCancels slow methods
@BulkheadNo isolationLimits concurrent executions

πŸ”₯ WHY Do These Fail in Self-Call?

Because Spring uses Proxy-Based AOP (Aspect-Oriented Programming).

In Simple Words:

Spring wraps your bean like this:

[ProxyBean (adds features like @Transactional)]
         |
         ↓
     [RealBean]

If someone else calls the method → It goes through proxy ✅
If you call your own method → You talk to yourself ❌

🧬 Diagram — What Happens Behind the Scenes

YOU (Controller/Another Class)
      |
 call bean.method()
      ↓
[ Spring Proxy Layer ]
(Starts transaction / checks cache / etc.)
      ↓
  [ Your Method ]

❌ But if you do this.someMethod() from inside the same class:

YOU (Same Class)
   |
call this.method() → Direct call
   ↓
[ Your Method ]  ← Skips Proxy = No annotation behavior

⚖️ Rules: When @Transactional Won’t Work (Must-Know!)

🚧 Scenario ✅ Will It Work? 🧠 Why
Method is privateProxy can’t access
Method is finalProxy can't override
Self-invocation (same class)Proxy not involved
Checked exception w/o rollbackForNo rollback
Non-Spring-managed classNo proxy created
Called from another beanGoes through proxy
Public + throws RuntimeExceptionAll Spring conditions met

πŸ§ͺ Real-Time Example — Happened to Me 🀦‍♂️

@Service
public class UserService {

    @Cacheable("userCache")
    public User getUser(Long id) {
        // fetch from DB
    }

    public User getUserWithLog(Long id) {
        log.info("Fetching user...");
        return getUser(id); // ❌ self-invocation = no cache
    }
}

Result:
❌ No cache hit
❌ DB called every time
😒 I cried, again

πŸ› ️ Fix Options

1️⃣ Move method to another bean

@Service
public class CacheService {
   @Cacheable("userCache")
   public User getUser(Long id) { ... }
}

@Service
public class UserService {
   @Autowired CacheService cacheService;

   public User getUserWrapper(Long id) {
      return cacheService.getUser(id); // ✅ proxy works
   }
}


🀹 Funny Analogy

You wear a parachute (@Transactional) but jump from a trampoline inside your house.
The parachute doesn’t open. You didn’t go outside (i.e., through proxy). πŸ˜…

πŸ™‹ Wrapping Up

✅ If your Spring annotation isn’t working — ask this:
  • ❓ Is the method being called from another bean?
  • ❓ Is the method public?
  • ❓ Is there any self-invocation?
  • ❓ Is the annotation even meant to work in this flow?

πŸ“’ Stay Tuned for Tomorrow's Post

πŸ”œ Tomorrow we go deeper:
“Why @Transactional only rolls back on RuntimeException – And how to fix it”
πŸ’£ Spoiler: It’s not as simple as it looks!

Comments

Popular posts from this blog

πŸ” Is final Really Final in Java? The Truth May Surprise You 😲

πŸ’¬ “When I was exploring what to do and what not to do in Java, one small keyword caught my eye — final . I thought it meant: locked, sealed, frozen — like my fridge when I forget to defrost it.”   But guess what? Java has its own meaning of final… and it’s not always what you expect! πŸ˜… Let’s break it down together — with code, questions, confusion, jokes, and everything in between. 🎯 The Confusing Case: You Said It's Final... Then It Changed?! 🫠 final List<String> names = new ArrayList <>(); names.add( "Anand" ); names.add( "Rahul" ); System.out.println(names); // [Anand, Rahul] 🀯 Hold on... that’s final , right?! So how on earth is it still changing ? Time to dive deeper... 🧠 Why Is It Designed Like This? Here’s the key secret: In Java, final applies to the reference , not the object it points to . Let’s decode this like a spy mission πŸ•΅️‍♂️: Imagine This: final List<String> names = new ArrayList <>(); Be...

🌟 My Journey – From Zero to Senior Java Tech Lead 🌟

 There’s one thing I truly believe… If I can become a Java developer, then anyone in the world can. πŸ’― Sounds crazy? Let me take you back. πŸ•“ Back in 2015… I had zero coding knowledge . Not just that — I had no interest in coding either. But life has its own plans. In 2016, I got a chance to move to Bangalore and joined a Java course at a training center. That’s where it all started — Every day, every session made me feel like: "Ohhh! Even I can be a developer!" That course didn’t just teach Java — it gave me confidence . πŸ§ͺ Two Life-Changing Incidents 1️⃣ The Interview That Wasn't Planned Halfway through my course, I had to urgently travel to Chennai to donate blood to a family member. After that emotional rollercoaster, I found myself reflecting on my skills and the future. The next day, as I was preparing for my move to Bangalore to complete the remaining four months of my course, I randomly thought — "Let me test my skills... let me just see...

🎒 Java Loops: Fun, Fear, and ForEach() Fails

πŸŒ€ Oops, I Looped It Again! — The Ultimate Java Loop Guide You Won't Forget “I remember this question from one of my early interviews — I was just 2 years into Java and the interviewer asked, ‘Which loop do you prefer and why?’” At first, I thought, “Duh! for-each is cleaner.” But then he grilled me with cases where it fails. 😡 That led me to explore all loop types, their powers, and their pitfalls. Let’s deep-dive into every major Java loop with examples &  real-world guidance so you'll never forget again. πŸ” Loop Type #1: Classic For Loop — “The Old Reliable” ✅ When to Use: You need an index You want to iterate in reverse You want full control over loop mechanics ✅ Good Example: List<String> names = List.of("A", "B", "C"); for (int i = 0; i < names.size(); i++) { System.out.println(i + ": " + names.get(i)); } πŸ”₯ Reverse + Removal Example: List<String> item...