Hibernate lets you choose a second-level cache concurrency strategy per entity: READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, or TRANSACTIONAL. What consistency does each guarantee, and what kind of data is each meant for?
answer
- read-only = never mutated, updates throw
- nonstrict = no locks, evict, short stale window
- read-write = soft lock, readers miss, ~read-committed
- transactional = JTA/XA, atomic with DB
- @Cache(usage=...) is per entity
basics
~20 sREAD_ONLY: never-modified data; updates are rejected; fastest. NONSTRICT_READ_WRITE: no locking, entry evicted on change, short stale window possible. READ_WRITE: soft locks give roughly read-committed consistency for mutable data. TRANSACTIONAL: cache enlists in the JTA transaction; strongest, slowest.
solid answer
~50 sThe strategy tells Hibernate how much work to do to keep a shared cache region consistent with the database, and it is chosen per entity. - **READ_ONLY** — for rows never modified after insert (lookup codes, reference data). No locking, no version checks; attempting an update throws. Cheapest and safest. - **NONSTRICT_READ_WRITE** — no locking at all. On a write Hibernate *evicts* the entry rather than replacing it, so a concurrent reader can briefly observe a stale value. Fits data that changes rarely and tolerates a short stale window. - **READ_WRITE** — the workhorse for mutable data. Uses *soft locks*: while a write is in flight the entry is replaced by a lock marker, so concurrent readers miss the cache and go to the database. Gives approximately read-committed semantics on a non-transactional cache. - **TRANSACTIONAL** — the cache is enlisted in the same JTA transaction as the database, so both commit or roll back together. Strongest guarantee, highest cost, needs JTA plus a transactional-capable provider.
code
java · 14 lines@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Country { /* ... */ }
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Account { /* ... */ }
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class TaxRate { /* ... */ }go deeper
Be able to name the four strategies and match each to a data shape: never-changes, rarely-changes, regularly-changes, must-be-atomic.
Explain the mechanism behind each — no locking vs. soft locking vs. XA enlistment — and that the choice is per entity via the usage attribute of Hibernate's cache annotation.
Discuss what staleness each admits, the cost each adds to writes, and why a hot write path may be better off uncached than cached with READ_WRITE.
Frame it as a consistency-versus-throughput budget per entity class, including the operational cost of a distributed cache and the fact that TRANSACTIONAL forces a JTA architecture on the whole system.
## Why a strategy exists at all The second-level cache is shared by every persistence context in the process, and with a clustered provider potentially across nodes. That makes it shared mutable state living *outside* the database, and the database is the only component that actually enforces transactions on your data. So when a transaction changes a row, something has to decide what happens to the cached copy: replace it, throw it away, block readers, or coordinate with the transaction. Hibernate does not guess. You declare a **cache concurrency strategy** per entity (and per collection), and that declaration is the contract about how much staleness you are willing to accept in exchange for how much coordination cost. The strategy is declared with Hibernate's `@Cache(usage = ...)` annotation. The JPA-standard `@Cacheable` annotation only says *whether* to cache, not how, so when only `@Cacheable` is present Hibernate falls back to the `hibernate.cache.default_cache_concurrency_strategy` setting or the region factory's own default. ## READ_ONLY Use it for data that is inserted and then never updated: currency codes, country lists, immutable audit rows, product catalogue entries that are versioned by insert rather than edited. Because nothing ever changes, there is no consistency problem to solve. Hibernate does no locking, keeps no version metadata for reconciliation, and may hand out the cached state directly. If application code does modify such an entity, Hibernate refuses at flush time with an exception rather than silently caching a lie. Deletes are handled by simply dropping the entry. This is the fastest and the only strategy that is *unconditionally* correct. ## NONSTRICT_READ_WRITE This is the "cheap and slightly wrong" option for data that is technically mutable but almost never mutated. There is no locking whatsoever. When a transaction updates the entity, Hibernate does not put the new state into the cache; it **removes** the entry — at flush time and again after the transaction completes — so the next reader reloads from the database and repopulates. The gap between the database commit and the cache removal, plus the possibility that a concurrent reader loads the pre-commit row and writes it into the cache after the removal, means another session can read a stale value for a short window. Hibernate documents this openly: the strategy makes no guarantee that the cached item is the latest version. Combine it with a modest time-to-live so any stale entry cannot survive long. ## READ_WRITE This is the strategy for genuinely mutable, read-mostly entities where you cannot tolerate obviously stale reads. It gets close to read-committed consistency on a cache that has no transactions of its own by using a **soft lock**: before the write, the cached entry is replaced by a lock marker; while that marker is present, readers treat the region as a miss and go to the database, and competing writers cannot install a value. After the transaction completes, the lock is replaced by the new state (on commit) or simply released so the next read reloads (on rollback, or when several writers contended). Locks carry an expiry so a crashed process cannot poison a region forever. The cost is extra puts, extra cache round-trips, and cache misses for readers during writes — which is exactly why it is a poor fit for hot, frequently written rows. ## TRANSACTIONAL Here the cache itself is transactional and is enlisted as a resource in the same JTA transaction as the database, typically via two-phase commit. Cache and database therefore reach the same decision: commit both or roll back both, with no window in which one has the change and the other does not. It also means changes made in your transaction are visible to *your* subsequent reads through the cache, consistently. The price is a JTA transaction manager, a provider that supports transactional access, and 2PC overhead on every write. Most applications running on a local resource-local transaction setup cannot use it at all. ## How they line up Ordered by increasing strength and cost: READ_ONLY (no writes possible), NONSTRICT_READ_WRITE (eventual, brief staleness), READ_WRITE (roughly read-committed, no dirty reads from cache), TRANSACTIONAL (atomic with the database). Note that none of them gives you repeatable-read or serializable semantics across transactions — the second-level cache is an optimisation layered on top of whatever the database enforces, not a replacement for it. The right answer in an interview is not "always READ_WRITE"; it is that the strategy follows the entity's mutability and write rate.
- If an entity is annotated only with the JPA `@Cacheable` annotation and no Hibernate `@Cache` annotation, which strategy is used?JPA's `@Cacheable` only expresses whether the entity is cacheable, not how concurrent access is handled. Hibernate then applies the value of `hibernate.cache.default_cache_concurrency_strategy`, or, if that is unset, the default access type advertised by the configured region factory — commonly READ_WRITE. Relying on that implicit default is a smell; declare the strategy explicitly per entity.
- Does any of these strategies give you repeatable reads across transactions?No. The second-level cache is an optimisation on top of the database, not an isolation mechanism. READ_WRITE avoids handing out values that were never committed and TRANSACTIONAL keeps cache and database atomic, but neither changes the isolation level of your database transactions. Repeatable-read style guarantees still come from the database (and, within a single transaction, from the persistence context's identity map).
Think of a whiteboard summary of a ledger. READ_ONLY is a printed sign nobody edits. NONSTRICT is wiping the board after a change and letting anyone redraw. READ_WRITE tapes a 'being updated' note over it so readers consult the ledger directly. TRANSACTIONAL updates board and ledger in the same locked step.
saying these in an interview costs you the question
- Saying READ_WRITE is always the safe default, so every cached entity should use it
- Believing NONSTRICT_READ_WRITE writes the new value into the cache — it evicts instead
- Thinking the second-level cache gives transaction isolation or repeatable reads by itself
- Assuming TRANSACTIONAL works without a JTA transaction manager and a transactional cache provider
- Claiming the strategy is a global setting rather than a per-entity choice