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...

🎒 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...

🧡 Virtual Threads in Java — The Ultimate Guide with Diagrams, Code & Interview Qs!

πŸš€ “How are Virtual Threads different from Thread Pools?” 😡 “Are they OS threads or JVM threads?” πŸ™ƒ “Should I still use CompletableFuture?” 🀯 “How do I even use them in real-time microservices?” 🧠 What are Virtual Threads? Virtual Threads (introduced in Java 21 as stable πŸŽ‰) are lightweight threads managed by the JVM instead of the OS kernel. πŸ‘‰ They look like normal threads, but don’t hog OS resources like traditional threads. 🧠 What is the OS Kernel? πŸ›️ OS Kernel = The Brain of the Operating System It’s the core part of your OS (Windows, Linux, Mac) that: Manages memory 🧠 Schedules threads πŸ•’ Talks to hardware πŸ’» Handles I/O operations πŸ“¨ When you create a traditional thread in Java, the JVM asks the OS Kernel to create a real OS-level thread. πŸ–Ό️ Imagine This... ┌───────────────────────────┐ │ Your Java Application │ └────────────┬──────────────┘ │ ...