At first, Spring Data JPA felt amazing.
I wrote:
repository.save(entity);
Data magically appeared in the database.
But then strange things started happening.
- Updates didn’t persist
- Deletes didn’t behave as expected
- Child records disappeared (or didn’t)
- Entities acted… alive
That’s when I realized:
👉 I didn’t understand what Hibernate was actually doing.
🏗️ The Big Stack (Clear Mental Model)
Before diving deep, here’s the actual hierarchy:
👉 Spring Data JPA → abstraction (repositories)
👉 JPA → specification (rules)
👉 Hibernate → implementation (ORM engine)
👉 JDBC → executes SQL
👉 Database
Spring Data JPA reduces boilerplate.
Hibernate does the heavy lifting.
JDBC talks to the database.
🧬 Hibernate Entity Lifecycle (The Missing Piece)
Every JPA entity lives in one of these states.
Understanding this changed everything for me.
🟡 Transient
Patient p = new Patient();
- Just a Java object
- Not tracked by Hibernate
- Not stored in DB
If garbage collected → gone forever.
🟢 Persistent
Triggered by:
- persist()
- save()
- find()
- get() / load()
Now:
- Entity is managed
- Tracked inside Persistence Context
- Changes are auto-synced to DB
👉 This is where Hibernate magic happens.
🔵 Detached
Triggered by:
- detach()
- clear()
- close()
Entity:
- Exists in DB
- Exists in memory
- But Hibernate stops tracking it
Changes won’t be saved unless you merge().
🔴 Removed
Triggered by:
- remove()
- delete()
Entity:
- Marked for deletion
- Deleted during transaction commit
🧠 EntityManager & Persistence Context (VERY IMPORTANT)
Hibernate doesn’t hit the DB immediately.
It first checks the Persistence Context.
Flow:
-
find()→ checks Persistence Context - If found → return object
- If not → DB SELECT
- Store entity in Persistence Context
👉 This is why multiple finds don’t always hit the DB.
🔗 Relationships: Owning Side vs Inverse Side
This is where many bugs are born.
🔑 Owning Side
- Controls the foreign key
- Updates actually affect DB
🔄 Inverse Side
- Only reflects relationship
- Cannot update foreign key
👉 Updating inverse side alone does nothing in DB.
🏥 One-to-Many Example (Patient & Appointments)
- Patient = Parent
- Appointment = Child
If Patient is deleted → Appointments should also go.
That’s why:
👉 Patient is the owning lifecycle controller
🔁 Cascading: Let Hibernate Do the Work
Cascading tells Hibernate:
“When I do something to the parent, do the same to children.”
Common Cascade Types:
- PERSIST → save child automatically
- MERGE → update child
- REMOVE → delete child
- REFRESH
- DETACH
- ALL
Example:
- Save Patient → Appointments saved automatically
- Delete Patient → Appointments deleted automatically
No manual repository calls needed.
🧹 orphanRemoval = true (POWERFUL & DANGEROUS)
This one surprised me.
What it does:
- Deletes child entity when it’s removed from parent collection
- Parent still exists
Example:
patient.getAppointments().remove(appointment);
👉 Appointment gets deleted from DB automatically.
orphanRemoval vs CascadeType.REMOVE
| Feature | CascadeType.REMOVE | orphanRemoval |
|---|---|---|
| Parent deleted | ✅ Child deleted | ✅ Child deleted |
| Child removed from collection | ❌ | ✅ |
| Parent still exists | ❌ | ✅ |
🎯 When to Use orphanRemoval
Perfect when:
- Child has no meaning without parent
- Appointment without Patient
- Insurance without User
⚠️ Use carefully — deletions are automatic.
⚠️ The Mistakes I Was Making
I used to:
- Call save() everywhere
- Ignore entity states
- Manually delete children
- Get confused by unexpected deletes
Once I understood:
- Entity lifecycle
- Persistence Context
- Cascades & orphanRemoval
👉 Hibernate stopped feeling random.
🚀 Final Thoughts
Hibernate is not magic.
It’s state + rules + lifecycle.
If JPA ever feels:
- Unpredictable
- Dangerous
- Confusing
You’re missing this layer of understanding.
This post is part of my learning-in-public journey while exploring Spring Boot, JPA, and real-world backend behavior.
Did Hibernate ever delete or update something you didn’t expect?



Top comments (0)