The Persistence Context & Entity States
The Persistence Context & Entity States
When Hibernate manages your objects, every object lives in a precisely defined state relative to the persistence context — the in-memory workspace that tracks all entity instances known to the current EntityManager. Understanding these states is not optional knowledge: it determines whether your changes are saved, silently ignored, or trigger a lazy-loading exception in production.
What Is the Persistence Context?
The persistence context is the unit of work between your application code and the database. Conceptually it is a map from entity identity (type + primary key) to a live Java object. Every EntityManager instance owns exactly one persistence context. In a typical Spring Boot application the context is scoped to a single transaction: it opens when the transaction begins and is flushed and closed when the transaction commits.
EntityManager is the API you interact with; the persistence context is the internal cache it maintains. One EntityManager always has exactly one persistence context, but you can configure extended contexts that survive beyond a single transaction.
The Four Entity States
1 — Transient
An object is transient when it has just been constructed with new and has never been associated with any persistence context. Hibernate knows nothing about it. No database row corresponds to it yet, and if you discard the reference the object is garbage-collected with no side effects.
2 — Managed (Persistent)
An object is managed when it has been associated with the current persistence context. This happens via em.persist(entity) (new objects), em.find(), em.merge(), or JPQL queries. While an entity is managed, Hibernate watches it. Before the transaction commits, it compares every managed object's current state to the snapshot it took at load time — a process called dirty checking — and emits the necessary UPDATE statements automatically.
save() inside a transaction. If you fetched or persisted an entity inside the same transaction, any field changes are tracked automatically. This is why Spring Data JPA's save() is only strictly required for transient entities or for detached entities being re-attached; calling it on an already-managed entity is a no-op.
3 — Detached
An object is detached when it previously was managed but its persistence context has closed (the transaction ended, or you called em.detach(entity) or em.clear()). The object still holds its primary key and its last-known field values, but Hibernate is no longer watching it. Changes made to a detached object are silently ignored unless you explicitly re-attach it.
LazyInitializationException because the persistence context is already closed. Solutions: fetch eagerly with a JOIN FETCH query, use a DTO projection, or enable the Open-Session-in-View pattern (enabled by default in Spring Boot — but understand its trade-offs before relying on it in production).
4 — Removed
An object is removed when you call em.remove(managedEntity) on a managed entity. It remains in the persistence context until the transaction commits, at which point Hibernate fires the DELETE statement. Accessing a removed entity's state before the commit is technically valid but rarely meaningful.
State Transition Diagram
The four states and their transitions form a well-defined lifecycle:
- new → managed:
em.persist(entity) - db row → managed:
em.find(), JPQL query,em.merge() - managed → detached: transaction ends,
em.detach(),em.clear(),em.close() - detached → managed:
em.merge(detached)returns a new managed copy - managed → removed:
em.remove(entity) - removed → managed:
em.persist(removedEntity)before the transaction commits
Why This Matters for Performance
Dirty checking scans every managed entity at flush time. If a single transaction loads hundreds of entities, Hibernate must compare every one of them — even those you never intended to modify. This is a real production performance concern. Strategies to mitigate it:
- Use read-only transactions (
@Transactional(readOnly = true)) for queries. Spring tells Hibernate to skip dirty checking entirely. - Prefer DTO projections (JPQL
SELECT new com.example.OrderDto(o.id, o.reference) FROM Order o) when you only need data, not entities you intend to update. - Call
em.detach(entity)explicitly after reading an entity you know you will not modify, to remove it from the dirty-check scan.
@Transactional(readOnly = true). It is a free performance gain: Hibernate skips the flush entirely, and some JDBC drivers (and read replicas) can apply additional optimizations.
Flush Modes
Hibernate does not necessarily flush (synchronize the context to the database) only at commit. The default flush mode is AUTO: Hibernate will also flush before executing a JPQL query if the pending changes could affect the query's results. This is correct but can surprise developers who expect changes to be invisible until commit. You can change the flush mode per EntityManager with em.setFlushMode(FlushModeType.COMMIT), but AUTO is the safe default.
Summary
Every entity is always in one of four states: transient (unknown to Hibernate), managed (tracked for changes), detached (known by identity but not tracked), or removed (scheduled for deletion). The transitions between these states are driven by EntityManager operations and transaction boundaries. Mastering this lifecycle is what separates developers who fight Hibernate from those who work with it efficiently.