DEV Community

Cover image for 🐌 My Spring Boot API Was Fast… Until Relationships Caused N+1 Queries
Shashwath S H
Shashwath S H

Posted on

🐌 My Spring Boot API Was Fast… Until Relationships Caused N+1 Queries

FetchType and Orphan Removal in Relational Queries, N+1 Query Optimization

When I first built relational APIs using Spring Data JPA, everything worked fine.

Entities saved properly ✅
Relationships mapped correctly ✅
Responses returned clean JSON ✅

But then I noticed something scary:

The API got slower as the data increased.

No code changes.
No heavy computation.
Just… slower.

That’s when I discovered the hidden performance killers:

FetchType
orphanRemoval
N+1 Query problem


🧠 FetchType: The Silent Decision Maker

Whenever you map a relationship like:

  • @OneToMany
  • @ManyToOne
  • @OneToOne
  • @ManyToMany

Hibernate has to decide:

“Should I load related data now… or later?”

That decision is controlled using FetchType.


🔥 Two Fetch Strategies in JPA

FetchType.EAGER

Hibernate loads related data immediately.

That means:

  • Parent loads ✅
  • Child loads ✅
  • Even if you don’t need child data ❌

This can lead to:

  • Heavy queries
  • Bigger payloads
  • Slower APIs

FetchType.LAZY

Hibernate loads related data only when needed.

That means:

  • Parent loads ✅
  • Child loads only when accessed ✅

This is usually better for performance…

But it introduces real-world issues if you're not careful.


⚠️ The Real Problem: N+1 Queries

This is where performance gets destroyed quietly.

What happens in N+1?

You fetch:

  • 1 query for parent list ✅ Then for each parent:
  • 1 query for its children ❌ ❌ ❌

So if you have 10 parents:

👉 1 (parent query) + 10 (child queries) = 11 total queries

If you have 100 parents:
👉 101 queries

That’s why it’s called N+1.


💥 Why N+1 Happens So Often

Because with LAZY loading, Hibernate doesn’t fetch children in the first query.

So when you loop through results and access relations, Hibernate keeps firing extra SELECT queries.

And the scary part?

✅ Your code looks clean
❌ Your database gets abused


🛠️ How to Fix / Optimize N+1 Queries

N+1 is not “a small issue”.
It’s a performance bug.

Common ways to fix it include:

  • Fetching required relations efficiently (instead of triggering lazy loads repeatedly)
  • Using optimized fetch strategies in queries
  • Designing API responses properly (DTOs / projections)

The key is:

Fetch what you need — not everything, and not one-by-one.


🧹 Orphan Removal: Helpful… and Dangerous

When I saw orphanRemoval = true, I thought it was just cleanup.

But orphan removal means:

When a child is removed from the parent’s collection, it gets deleted from the DB automatically.

Example idea:

  • Patient has appointments
  • Remove an appointment from list
  • Hibernate deletes that appointment record ✅

This is useful when:
✅ Child cannot exist without parent

But it can also cause unexpected deletions if used blindly.


✅ orphanRemoval vs Cascade REMOVE (Quick Clarity)

CascadeType.REMOVE

Child gets deleted when parent is deleted

orphanRemoval = true

Child gets deleted when it is no longer referenced by parent
(even when parent still exists)


⚠️ The Mistakes I Made

I used to:

  • Use EAGER without thinking
  • Use LAZY and accidentally trigger N+1 queries
  • Enable orphanRemoval and forget about it
  • Wonder why performance dropped randomly

Once I understood FetchType + N+1:

  • APIs became faster
  • Queries became predictable
  • Debugging got easier

🚀 Final Thoughts

Relationships in Spring Data JPA are easy to write…

…but hard to scale if you ignore performance.

If your Spring Boot API is slow and you don’t know why:

Check:
FetchType
N+1 queries
orphanRemoval behavior

This post is part of my learning-in-public journey as I explore Spring Boot and real-world backend optimization.

Have you ever noticed your API getting slower after adding relationships?

Top comments (0)