Beyond holding the query text, a JPA @NamedQuery declaration can carry query hints and a lock mode. What can you attach there, and why would you prefer attaching it to the declaration over setting it at each call site?
answer
- readOnly → no snapshot, no dirty checking
- fetchSize → JDBC round-trip size on big scans
- query.timeout ms (portable) vs Hibernate seconds
- comment → identify SQL in slow-query logs
- Unknown hints are silently ignored
basics
~20 s@NamedQuery accepts a lockMode plus @QueryHint entries such as timeout, JDBC fetch size, read-only mode, query-cache participation and a SQL comment. Attaching them to the declaration makes the policy travel with the query, so no call site can forget it and every caller gets identical behaviour.
solid answer
~60 s```java @NamedQuery( name = "Book.findRecentReadOnly", query = "select b from Book b where b.published > :since", hints = { @QueryHint(name = "org.hibernate.readOnly", value = "true"), @QueryHint(name = "org.hibernate.fetchSize", value = "200"), @QueryHint(name = "jakarta.persistence.query.timeout", value = "5000"), @QueryHint(name = "org.hibernate.comment", value = "recent-books-report") }) ``` Useful hints: `org.hibernate.readOnly` (entities loaded without a dirty-checking snapshot — less memory, no accidental UPDATE), `org.hibernate.fetchSize` (JDBC row prefetch, decisive on some drivers for large reads), query timeout, `org.hibernate.cacheable` plus a cache region for the query cache, and `org.hibernate.comment` to tag the emitted SQL so it is identifiable in database monitoring. `lockMode` on the declaration makes the lock intrinsic: a query meant to be the read side of a read-modify-write can declare a pessimistic mode once instead of relying on every caller to remember. The value is policy locality: the intent ("this is a bulk read-only report scan") is stated where the query is defined, not scattered across call sites that will drift. Call sites can still override by setting a hint on the returned Query.
go deeper
Know that @NamedQuery can carry @QueryHint entries and that a timeout or read-only flag is set that way rather than at every call.
Name a few concrete hints and what they do, and explain why putting policy on the declaration prevents drift between call sites.
Bring operational experience: read-only plus fetch size for large scans, comments to identify SQL in slow-query logs, timeouts to stop a query holding a connection, plus the silent-ignore trap.
Frame it as where execution policy should live and who owns it, including whether a declared pessimistic lock mode is a safety feature or a hidden contention source across teams reusing the query.
## The declaration is a place to put policy A query has two halves: *what to fetch* (the JPQL) and *how to execute it* (timeout, fetch size, caching, locking, read-only). Inline queries force the second half onto each call site, where it is forgotten, copied inconsistently, or quietly dropped during a refactor. A named query declaration can carry both. ```java @NamedQuery(name = "...", query = "...", lockMode = LockModeType.PESSIMISTIC_WRITE, hints = { @QueryHint(name = "...", value = "...") }) ``` ## Hints worth knowing **`org.hibernate.readOnly = true`.** Entities returned are loaded into the persistence context *without* a loaded-state snapshot. Two consequences: dirty checking cannot flag them, so no accidental UPDATE can be generated from an incidental setter call, and memory per entity drops roughly by the size of the second copy of its state. For report-style queries that pull thousands of rows and only read them, this is the single highest-value hint. Note it changes semantics — modifications to those entities will not be persisted, which is the point but surprises people. **`org.hibernate.fetchSize`.** Sets the JDBC statement fetch size — how many rows the driver pulls per round trip. Drivers differ wildly: some default to fetching everything into client memory, and a fetch size is the difference between a streaming scan and an out-of-memory error. Pairing this with read-only is the standard shape for a large scan. **Timeouts.** `jakarta.persistence.query.timeout` (milliseconds) is the portable one; there is a Hibernate-specific equivalent in seconds. It bounds a runaway query rather than letting it hold a connection indefinitely. `jakarta.persistence.lock.timeout` bounds how long a pessimistic lock request waits. **`org.hibernate.cacheable = true`** enrols the query in the **query cache** (which caches the *identifiers* a query returned, not the entities themselves, and which requires the second-level cache to be usable at all). `org.hibernate.cacheRegion` names the region. This is a hint you attach only when the query is genuinely repetitive over stable data; wiring it in at some call sites and not others is exactly the inconsistency the declaration prevents. **`org.hibernate.comment`** injects a comment into the generated SQL (when SQL comments are enabled). In a database's slow-query log or monitoring view, the emitted SQL is otherwise anonymous; a comment naming the query is often what turns a mystery statement into a five-minute fix. Cheap and underused. **Flush mode** can also be pinned, which matters for queries that must not trigger an automatic flush of pending changes. ## lockMode on the declaration `lockMode = LockModeType.PESSIMISTIC_WRITE` makes the query acquire row locks as part of executing, rather than requiring the caller to call `setLockMode` or a separate lock request. Where the query *is* the read side of a read-modify-write sequence, encoding that in the declaration is a correctness improvement: the guarantee no longer depends on every author of every call site knowing the protocol. The flip side is that the lock becomes invisible at the call site, so someone reusing the query for a harmless read now takes write locks and can create contention or deadlocks. The mitigation is naming: a query that locks should say so in its name. ## Why declaration beats call site 1. **No call site can forget.** The policy is not opt-in per caller. 2. **Consistency.** Every caller gets the same fetch size and timeout, so behaviour is reproducible and a tuning change is one edit. 3. **Reviewability.** A reviewer looking at the query declaration sees both the shape and the execution policy together. With inline queries, finding out whether a query is cached means grepping the call sites. 4. **It composes with externalisation.** Hints in `orm.xml` can be tuned per environment without touching Java. ## Limits and cautions - **Unknown hints are ignored, silently.** JPA specifies that a provider must ignore hints it does not recognise. A misspelt hint name is not an error; it simply does nothing. This is a real trap — verify with SQL logging or metrics rather than trusting the annotation. - **Call sites can still override.** Setting a hint on the returned `Query` takes effect for that execution, so the declaration is a default rather than a guarantee. - **Hints are not portable.** `org.hibernate.*` names bind you to Hibernate; only the `jakarta.persistence.*` ones are specified. - **Do not turn hints into a tuning junk drawer.** Each hint should have a reason someone can state; a caching hint on a query over volatile data is worse than none. ## The shape of a good answer Name two or three hints you have actually used, say what problem each solved (memory on large scans, an unbounded query holding a connection, an unidentifiable statement in the slow-query log), and make the general point: the declaration is where execution policy belongs because it cannot be forgotten there.
- What actually changes inside Hibernate when a query is executed with the read-only hint?Entities are added to the persistence context without their loaded-state snapshot, the array of original values Hibernate normally keeps to compare against at flush. Without it there is nothing to diff, so dirty checking cannot produce an UPDATE for those instances, and memory per entity drops noticeably. They are still managed and still identity-mapped, so a later lookup of the same id returns the same instance.
- You add a query hint and nothing changes. How do you find out whether it took effect?First suspect the name: JPA requires providers to ignore unrecognised hints, so a typo or a hint from the wrong namespace fails silently. Confirm the actual behaviour rather than the annotation — enable SQL and statement logging, check the driver-level fetch size or the timeout in the database's session view, or watch query-cache metrics. If the hint is genuinely applied and behaviour is unchanged, the hint was not the bottleneck.
saying these in an interview costs you the question
- Assuming a misspelt or unsupported hint raises an error rather than being silently ignored
- Treating org.hibernate.cacheable as a general speed-up rather than a narrow query-cache opt-in over stable data
- Believing the query cache stores the entities themselves rather than the identifiers plus a second-level cache lookup
- Adding read-only to a query whose results are later modified and expecting the changes to persist
- Thinking hints on the declaration cannot be overridden by the call site