First-level vs second-level cache, enabling L2 caching (providers, annotations, regions, concurrency strategies), whether L2 is shared across sessions, when the query cache helps (and its parameter handling), evicting entities/regions, Spring @Cacheable vs Hibernate caches, consistency and stale-data risks, caching lazy associations (collection caches), and monitoring hit/miss statistics in production.
Published September 25, 2026
Hibernate caching is frequently misunderstood. The senior message is:
Short answer:
EntityManager/Session (usually per transaction or request);em.find twice returns the same object, without a second SELECT), and enables dirty checking and write-behind (the SQL is flushed at commit);SessionFactory (the application instance, or the cluster with a distributed provider);find and lazy-association loads.Learn it in depth → Caching Strategies
Short answer:
hibernate-jcache plus Ehcache 3, Caffeine (through its JCache adapter), Hazelcast or Infinispan (clustered); or a Redis-based integration (Redisson).spring:
jpa:
properties:
hibernate.cache.use_second_level_cache: true
hibernate.cache.region.factory_class: jcache
hibernate.javax.cache.provider: org.ehcache.jsr107.EhcacheCachingProvider
jakarta.persistence.sharedCache.mode: ENABLE_SELECTIVE # only @Cacheable entities
hibernate.generate_statistics: true # for tuning (with care in production)
@Entity
@jakarta.persistence.Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "country")
public class Country { @Id String code; String name; }
READ_ONLY: immutable data; the fastest;NONSTRICT_READ_WRITE: rare updates; brief staleness is acceptable;READ_WRITE: soft locks, giving consistency within one node;TRANSACTIONAL: JTA-capable providers.Short answer: Yes. It belongs to the SessionFactory, so every session (transaction) in that application instance shares it. Across multiple instances (pods), each has its own local L2 unless you use a clustered or distributed provider (Hazelcast, Infinispan, Redis-backed). With local caches, one pod's update doesn't invalidate the other pods' copies, which gives stale reads. Use invalidation-based clustering, short TTLs, or only cache effectively immutable data.
Short answer: The query cache stores query results as lists of entity IDs (or scalar values), keyed by the query string plus the parameter values (plus pagination). So yes, parameterised queries are cached, one entry per distinct parameter set. It's used only when:
hibernate.cache.use_query_cache=true;@QueryHints(@QueryHint(name = "org.hibernate.cacheable", value = "true")), or query.setHint(...)/setCacheable(true)).Entity results also need the entities in L2, or each cached ID triggers a SELECT (an N+1 from the cache).
It's useful when the same query runs very frequently with few distinct parameters, and the underlying tables rarely change: lookups of countries, categories or configuration. It's harmful otherwise: any insert, update or delete on a queried table invalidates every cached query touching that table (through update timestamps), so write-heavy tables thrash the cache, and add overhead.
Short answer: Use the JPA or Hibernate Cache APIs:
Cache cache = entityManagerFactory.getCache(); // JPA
cache.evict(Country.class, "IN"); // one entity
cache.evict(Country.class); // the whole entity region
cache.evictAll(); // everything
SessionFactory sf = entityManagerFactory.unwrap(SessionFactory.class);
sf.getCache().evictCollectionData("com.shop.Order.lines"); // a collection region
sf.getCache().evictQueryRegions(); // the query cache
sf.getCache().evictRegion("country");
When: after out-of-band changes (bulk SQL, another application writing to the database, data fixes), during deployments with data migrations, or through an admin endpoint.
@Cacheable affect Hibernate's caches?Short answer: No, they're independent layers. Spring's @Cacheable caches method return values (for example, DTOs from a service method) in a Spring CacheManager (Caffeine or Redis), keyed by method arguments. Hibernate's L2 caches entity state inside the ORM. So:
Choose deliberately: the Spring cache at the service level for computed or aggregated read models, and Hibernate L2 for frequently loaded reference entities.
Short answer:
NONSTRICT_READ_WRITE allows short windows of staleness.READ_ONLY).READ_WRITE (soft locks) for consistency on a single node, and clustered invalidation (Infinispan, Hazelcast) for multiple nodes.Short answer:
hibernate.generate_statistics=true gives Statistics.getSecondLevelCacheHitCount(), the miss and put counts, region statistics, and query cache hits. Exposed through Micrometer's Hibernate metrics (hibernate.second.level.cache.requests{result=hit|miss}) when enabled, to Prometheus and Grafana. Statistics have some overhead, so enable them selectively, or sample.org.hibernate.cache=DEBUG, plus SQL logging to confirm that SELECTs disappear.CacheMetricsRegistrar/cache.gets{result=hit|miss} (enable recordStats() for Caffeine).Short answer: Yes, through the collection cache. Annotate the collection with @Cache(usage = …). It caches the IDs of the collection elements, so the element entities must also be cacheable, otherwise each ID triggers a SELECT (an N+1 from the cache). @ManyToOne targets that are in L2 resolve from the cache when a lazy proxy is initialised.
The caveats:
LazyInitializationException: you still need an open session to initialise the proxy;@SQLRestriction) interact with the cached contents.Short answer: It's worth it when:
Otherwise, prefer:
@Cacheable with Caffeine or Redis) with explicit invalidation;Q: What is the persistence context's role in repeatable reads?
A: Within one session, loading the same entity again returns the same instance, with its in-memory state, even if the database changed in the meantime: application-level repeatable read. Use em.refresh() to reload it.
Q: Why can a long-running persistence context cause memory problems?
A: Every loaded entity stays managed (plus a snapshot for dirty checking) until the session ends. Batch jobs should flush() and clear() periodically, or use StatelessSession, or projections.
Q: How does READ_WRITE avoid stale reads during updates?
A: It puts a soft lock on the cache entry when an update starts. Readers that see the lock go to the database. The lock is replaced by the new state after commit (or expires on failure).
Q: Should you cache entities with lazy associations in Redis through Spring @Cacheable?
A: No. Serialising a Hibernate proxy, or an entity graph, is fragile (lazy initialisation errors, huge payloads). Cache purpose-built DTOs instead.