Query Cache
AdvancedThe query cache stores query result IDs; it is invalidated whenever any entity in the queried tables changes, making it only beneficial for rarely-changing reference data.
Overview
Hibernate's query cache stores the list of entity identifiers returned by a JPQL query, not the full entity data. It works in conjunction with the second-level entity cache: on a cache hit, Hibernate returns the IDs from the query cache, then retrieves each entity from the L2 cache (ideally). If any entity in the queried table is inserted, updated, or deleted, the entire query cache region for that table is invalidated. This makes the query cache effective only for truly static or rarely-changing reference data (country codes, product categories, configuration values). For frequently-updated tables, the query cache is a net negative due to constant invalidation and re-population.
Enabling the Query Cache
Enable the L2 cache and the query cache in application.properties, then mark individual queries as cacheable. Both L2 and query cache must be enabled — query cache alone is ineffective.
# application.properties
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory
spring.jpa.properties.javax.cache.provider=org.ehcache.jsr107.EhcacheCachingProvider
// Mark entity as cacheable
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Country { ... }
// Mark the query result as cacheable
@QueryHints(@QueryHint(name = "org.hibernate.cacheable", value = "true"))
List<Country> findAll();How Query Cache Invalidation Works
Each query cache entry is associated with the table(s) it reads. Any modification to a row in that table invalidates ALL query cache entries for that table. This is why the query cache is harmful for frequently-updated tables.
// Scenario: countryRepository.findAll() is cached
// Cache hit: returns [1, 2, 3, ...] IDs from query cache → entities from L2
// Now a new country is inserted:
em.persist(new Country("ZZ", "Test Country"));
em.flush();
// → Hibernate invalidates the ENTIRE query cache for the "countries" table
// Next call to findAll() → cache miss → full DB query
// Good use case: country/currency/timezone codes (change very rarely)
// Bad use case: findByStatus("ACTIVE") on orders table (changes every second)Query Cache Regions
Query cache entries live in named cache regions. You can configure TTL and max entries per region via Ehcache. Use a short TTL for reference data that changes infrequently but not never.
// Assign a named query cache region
TypedQuery<Country> query = em.createQuery("SELECT c FROM Country c", Country.class);
query.setHint("org.hibernate.cacheable", true);
query.setHint("org.hibernate.cacheRegion", "reference.countries"); // named region
// Ehcache configuration (ehcache.xml) — 1-hour TTL, max 500 entries
<cache alias="reference.countries">
<expiry>
<ttl unit="hours">1</ttl>
</expiry>
<heap>500</heap>
</cache>
// Monitor cache hit ratio
SessionFactory sf = em.unwrap(Session.class).getSessionFactory();
Statistics stats = sf.getStatistics();
log.info("Query cache hits: {}, misses: {}",
stats.getQueryCacheHitCount(), stats.getQueryCacheMissCount());Key Points to Remember
- 1Query cache stores result IDs, not entity data — it depends on L2 entity cache for full objects
- 2Any insert/update/delete on a cached table invalidates ALL query cache entries for that table
- 3Only useful for rarely-changing reference data (countries, currencies, config values)
- 4Both hibernate.cache.use_second_level_cache and use_query_cache must be true
- 5Mark a query as cacheable with @QueryHint("org.hibernate.cacheable", "true")
- 6Monitor hit/miss ratio via SessionFactory.getStatistics() to verify cache effectiveness
Interview Questions
Sign in to ask AriaWhat does the query cache store — full entities or just IDs?
Why is the query cache harmful for frequently-updated tables?
What condition must be true for a query cache hit to avoid a DB call?
How do you configure a TTL for query cache entries in Ehcache?
What is the query cache region and why would you use a named one?
Ask Aria about Query Cache
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.