Does Hibernate's query cache invalidate after native SQL or external writes?
0 reputation · 20 Jan 2025, 23:16 UTC
0 reputation · 20 Jan 2025, 23:16 UTC
For a low-traffic application, enabling Hibernate's query cache with hibernate.cache.use_query_cache=true and @Cacheable can reduce database round-trips and connection occupancy. The cache stores result identifiers rather than entity state, and Hibernate invalidates those entries only when entity state changes through the ORM session lifecycle. When rows are modified by native SQL, JDBC batch statements, or external tools, automatic invalidation is bypassed, potentially leaving query and second-level cache regions stale until a TTL expires or a manual eviction occurs.
The unresolved decision is whether to accept eventual consistency in exchange for lower database costs, or to disable caching and absorb the added latency and connection usage. This matters most when write paths cannot be fully centralized through the ORM. Behavior may also differ across Hibernate major versions and between bulk HQL statements and native SQL, so the exact write path should be tested.
Can the query cache be configured to remain consistent when some writes bypass the ORM, or must architects choose between stale reads and higher database load? Which cache provider settings or invalidation strategies are appropriate for a low-traffic workload with mixed write paths? Does Hibernate provide a documented mechanism to detect or refresh stale query cache regions after out-of-band updates?
35275 reputation · 21 Jan 2025, 09:35 UTC
No. Hibernate's query cache cannot be configured to stay automatically consistent when rows are modified by native SQL, JDBC batch statements, or external tools. If those writes bypass the ORM session lifecycle, the affected query cache entries are not invalidated. You must choose between accepting bounded staleness or disabling query caching for the affected regions.
Confirmed: The query cache stores result identifier sets, not entity state. Hibernate invalidates those identifiers only when entity state changes through the ORM session lifecycle (save, update, delete) or when a second-level cache eviction occurs. This boundary has persisted from Hibernate 3.x through 6.x.
Likely explanation: Native SQL and external writes produce no ORM events, so Hibernate has no signal to evict query cache regions. TTL expiration bounds staleness by time, not by actual data change.
For a low-traffic application, the following strategies apply:
@Cacheable or set hibernate.cache.use_query_cache=false to avoid serving stale identifiers.sessionFactory.getCache().evictQueryRegion("regionName") or use the CacheManager API. This requires application-level coordination.CacheMode.IGNORE on sessions that must observe the latest state immediately after an out-of-band write.Hibernate does not document a built-in listener or mechanism to detect out-of-band updates and refresh query cache regions automatically. Custom cache-provider listeners (Ehcache, Infinispan) can extend invalidation, but they add configuration complexity and are not part of the default query cache.
Enable the query cache, run a cached query, then update the underlying rows via native SQL. Re-run the query; if the result identifiers do not change, the region is stale. A unit test that calls evictQueryRegion after the native write should confirm that manual eviction refreshes the cache.
One diagnostic detail would change the recommendation: are your out-of-band writes rare and batchable, or continuous and unpredictable? If rare and batchable, short TTLs plus post-batch eviction may suffice. If continuous, disabling query caching for those regions is the safer choice.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 21 Jan 2025, 09:20 UTC
Native SQL, JDBC batch updates, or any external tool that modifies the table bypasses the Hibernate ORM session, so the query cache entries that are keyed by the entity identifier and the exact query remain valid until the region’s TTL expires or you manually evict it.
You can configure a TTL on the query cache region (e.g., hibernate.cache.query_cache_ttl) to bound staleness, or invoke SessionFactory.getCacheManager().evictQueryRegion("my-query") after each out‑of‑band write to force refresh.
In Hibernate 6 the query cache is a separate second‑level region and supports programmatic eviction via the CacheManager, while earlier 5.x versions required explicit eviction of the whole session‑level cache.