LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 1917 min read

The First-Level Cache

Understand the Hibernate Session-scoped first-level cache — why repeated get() calls for the same entity within one Session never hit the database twice.

Introduction

If you call session.get(Student.class, 1L) twice within the same Session, does Hibernate really run the SELECT statement twice? It does not — and you have actually been relying on this already without realizing it, every time dirty checking picked up a change to an entity you had loaded earlier. This behavior comes from the first-level cache, a cache that is built into every Session and cannot be turned off.

What You Will Learn
  • What the first-level cache is and why it always exists
  • How to observe it preventing a duplicate SELECT
  • Why the cache is scoped to a single Session, not the whole application
  • How to bypass or clear the cache when you need genuinely fresh data

What Is the First-Level Cache?

The first-level cache, also called the persistence context, is the in-memory map a Session keeps of every entity it has loaded or saved so far, keyed by entity type and primary key. It is mandatory and automatic — there is no annotation to enable it, and no way to disable it for a Session. Every Session has exactly one, and it exists purely for the lifetime of that Session.

AspectFirst-Level Cache
ScopeA single Session (persistence context)
Enabled by default?Yes, always — cannot be disabled
Shared across Sessions?No — each Session has its own, isolated cache
Configuration required?None

Seeing It in Action

Requesting the same entity by the same primary key twice within one Session only produces a single SELECT statement. The second call returns the exact same Java object reference from memory.

Session session = sessionFactory.openSession();
Student first = session.get(Student.class, 1L);
System.out.println("First call: " + first.getName());
Student second = session.get(Student.class, 1L);
System.out.println("Second call: " + second.getName());
System.out.println("Same object reference? " + (first == second));
session.close();
Console Output

Click Run to see what this code prints.

Notice there is only one "Hibernate: select ..." line in the output, even though get() was called twice. The second call was answered entirely from the first-level cache, without touching the database at all.

Cache Scope: One Session at a Time

The moment you close a Session and open a new one, the cache is gone. A second, independent Session has its own empty first-level cache, so loading the same entity again means a fresh SELECT is issued, even though the row has not changed.

Session session1 = sessionFactory.openSession();
Student s1 = session1.get(Student.class, 1L);
session1.close();
Session session2 = sessionFactory.openSession();
Student s2 = session2.get(Student.class, 1L); // fresh SELECT — new Session, new cache
session2.close();
System.out.println("Same object reference across sessions? " + (s1 == s2));
Console Output

Click Run to see what this code prints.

Not the Same as the Second-Level Cache

This Session-scoped behavior is specifically the first-level cache. Hibernate also supports an optional second-level cache that is shared across Sessions and survives beyond a single Session's lifetime — covered in the next lesson.

Bypassing and Clearing the Cache

Occasionally you need to guarantee you are reading the current database state within the same Session, bypassing whatever is cached. Session offers refresh() to reload a specific entity, and clear() or evict() to remove entities from the cache entirely.

Session session = sessionFactory.openSession();
Student student = session.get(Student.class, 1L);
session.refresh(student); // re-reads this entity from the database, overwriting cached state
session.evict(student); // removes just this entity from the first-level cache
session.clear(); // removes every entity currently cached in this Session
session.close();
Console Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Assuming the first-level cache is shared across different Sessions or across the whole application — it is not.
  • Expecting get() to always reflect the very latest database state within a long-lived Session that has cached an older version.
  • Confusing the first-level cache (always on, Session-scoped) with the second-level cache (optional, SessionFactory-scoped).
  • Calling session.clear() carelessly mid-transaction, which detaches every entity currently being tracked and can cause unexpected behavior with pending changes.

Best Practices

  • Keep Sessions short-lived and scoped to a single unit of work, so first-level cache staleness is rarely an issue.
  • Use refresh() only when you specifically expect another process to have modified the row since it was loaded.
  • Do not rely on the first-level cache as a substitute for an actual application-wide caching strategy — that is what the second-level cache is for.
  • Remember that dirty checking and the first-level cache are connected — Hibernate can only detect changes on entities it is currently tracking in that cache.

Frequently Asked Questions

No. It is a fundamental part of how a Session tracks entity state and enables dirty checking — there is no configuration option to turn it off.

It applies to any entity Hibernate loads, including HQL and Criteria query results. However, HQL queries still hit the database to determine which rows match, even if the resulting entities are then resolved from the cache by id.

evict() removes a single specified entity from the first-level cache, while clear() removes every entity currently tracked by the Session, effectively resetting the persistence context.

Key Takeaways

  • The first-level cache is a mandatory, Session-scoped cache of loaded entities.
  • Repeated get() calls for the same entity within one Session return the cached object, not a fresh SELECT.
  • The cache does not persist beyond the Session that created it.
  • refresh(), evict(), and clear() let you override cached state when you need to.

Summary

The first-level cache works quietly behind nearly everything you have written so far in this course, and understanding it explains behavior you have already seen, like dirty checking. Next, you will learn about the second-level cache — an optional cache that, unlike this one, is shared across Sessions and can dramatically reduce database load for frequently read, rarely changed data.

Next Lesson →

The Second-Level Cache