Sessions & Caching

The Session/EntityManager, first-level vs second-level cache, and the transient/persistent/detached entity lifecycle.

The Session / EntityManager

Hibernate's native API calls it a Session; the JPA-standard equivalent (implemented by Hibernate underneath) is EntityManager. Both represent the same thing: a single unit of work against the database, holding a persistence context — an in-memory registry of every entity currently being tracked.

Java
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();

User user = entityManager.find(User.class, 1L); // loaded and now tracked by the persistence context
user.setFullName("Ada Lovelace-Byron");           // no explicit save/update call needed

entityManager.getTransaction().commit(); // Hibernate detects the change and issues an UPDATE automatically

Notice there's no explicit update() call — this is dirty checking. Because the persistence context tracks the entity's original loaded state, on commit Hibernate compares the entity's current field values against what it originally loaded, and generates an UPDATE only for the fields that actually changed.

Entity states: transient, persistent, detached

Every entity instance is, at any moment, in exactly one of these states:

Plaintext
new User(...)              entityManager.persist(user)         entityManager.close() / detach()
     │                              │                                    │
     v                              v                                    v
 TRANSIENT ─────────────────>  PERSISTENT ─────────────────────>  DETACHED
 (plain object,                (tracked by the                    (was tracked,
  no DB row,                    persistence context,                but the context
  not tracked)                  changes auto-flushed)               is gone now)
State Meaning
Transient A plain new User(...) — Hibernate has never seen it, no row exists, no tracking happens
Persistent Loaded via find(), or after persist() — tracked by the current persistence context; field changes are automatically detected and flushed as SQL
Detached Was persistent, but its persistence context has since closed (or it was explicitly detach()ed) — the object still holds its data, but changes to it are no longer tracked or auto-saved
Java
User user;
try (EntityManager em = emf.createEntityManager()) {
    em.getTransaction().begin();
    user = em.find(User.class, 1L); // persistent
    em.getTransaction().commit();
} // em closes here — user becomes detached

user.setFullName("Changed after close"); // has NO effect on the database — nothing is tracking this object anymore

To persist further changes to a detached entity, it must be reattached with merge(), which returns a new, managed copy:

Java
EntityManager em2 = emf.createEntityManager();
em2.getTransaction().begin();
User managed = em2.merge(user); // returns a persistent copy with the new fullName
em2.getTransaction().commit();  // NOW the update is actually flushed

First-level cache (session-scoped)

The first-level cache is the persistence context itself — mandatory, always on, and scoped to a single Session/EntityManager. Requesting the same entity by ID twice within the same session returns the same object instance without a second database round-trip:

Java
User first = entityManager.find(User.class, 1L);  // hits the database
User second = entityManager.find(User.class, 1L); // returns the SAME object from the first-level cache — no query

This is why the first-level cache can't be disabled — it's inherent to what a persistence context is: a registry of everything that's been loaded or saved in this unit of work.

Second-level cache (optional, shared across sessions)

The second-level cache is an optional, separately configured cache (backed by a provider such as Ehcache or Caffeine) that's shared across sessions, letting one session benefit from data another session already loaded:

Java
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Product {
    // rarely-changing reference data is a good candidate
}
First-level Second-level
Scope One Session/EntityManager Shared across sessions/the whole application
Enabled by default? Yes, always No — opt-in, requires a cache provider
Best for Every entity, automatically Read-heavy, rarely-changing data (reference/lookup tables)
Risk None — it's just normal unit-of-work behavior Stale data if updated outside the cached session, or misused for frequently-changing entities

Common mistakes

  • Modifying a detached entity and expecting the change to reach the database without merge() — detached objects are inert as far as persistence is concerned.
  • Enabling the second-level cache for frequently-updated entities, where the cost of keeping the cache correctly invalidated outweighs the benefit — it's best suited to rarely-changing, read-heavy data.
  • Assuming the first-level cache is something you configure — it's inherent to the persistence context and can't meaningfully be turned off.
  • Holding a reference to an entity across a much longer scope than its originating session/transaction, then being surprised that changes silently don't persist.