Does a JPQL bulk UPDATE touch an entity's version column, and what does the HQL keyword in 'update versioned Customer c set c.status = :s' actually do?
answer
- plain bulk = version untouched
- stale holder overwrites, no conflict raised
- update versioned = increment, not check
- HQL only, no versioned delete
- clear the session afterwards
basics
~20 sA plain bulk UPDATE leaves the version column unchanged, so other sessions holding the row keep believing their copy is current. HQL's 'update versioned' makes Hibernate increment the version column as part of the same statement.
solid answer
~50 sBy default a bulk UPDATE writes exactly the columns in your SET clause. The version column is not among them, so rows change while their version stays the same. Anyone holding a loaded copy will later write it back with a matching version and no conflict is detected - the bulk change is silently lost. HQL adds the versioned keyword: 'update versioned Customer c set c.status = :s where ...' emits set status = ?, version = version + 1. That does not perform a version check - a bulk statement never compares versions - it only bumps them, so concurrent holders of those rows fail on their next write instead of overwriting you. Caveats: it is Hibernate-specific, not portable JPQL; the entity must actually declare a version attribute; and support depends on the version type. Bulk DELETE has no versioned form. If a workflow really needs per-row conflict detection, do not use a bulk statement.
code
java · 6 linesint rows = session.createMutationQuery(
"update versioned Customer c set c.status = :status "
+ "where c.lastLogin < :cutoff")
.setParameter("status", Status.ARCHIVED)
.setParameter("cutoff", cutoff)
.executeUpdate();go deeper
Know that a bulk update does not change the version column and that HQL has a keyword which makes it do so.
Walk the lost-update race concretely: version stays, stale holder writes back, no error raised.
Judge when bumping versions is right - rows held by interactive sessions - versus when it just creates noise, and remember the session still needs clearing.
Set the policy for batch mutations against user-editable data: who invalidates whom, whether conflicts surface to users, and where bulk mutation is allowed at all.
## The gap a bulk statement leaves Optimistic version columns work because every entity write includes the version in the WHERE clause and increments it in the SET clause; a write that matches zero rows means somebody else changed the row first. Bulk statements sit outside that protocol entirely. They neither check nor increment: 'update Customer c set c.status = ARCHIVED where c.lastLogin < :cutoff' becomes plain SQL touching only the status column. That produces a specific, nasty race. Session A loads customer 42 at version 7. A bulk statement archives customer 42; the row now has status ARCHIVED and version still 7. Session A edits a different field and flushes: update customer set name=?, status=?, version=8 where id=42 and version=7. One row matches, so no conflict is reported - and the UPDATE carries A's stale status, restoring the pre-bulk value. The bulk change is gone, quietly. ## What 'versioned' does HQL extends the update statement with a keyword placed before the entity name: 'update versioned Customer c set c.status = :status where c.lastLogin < :cutoff'. Hibernate then adds the version column to the generated SET clause, incrementing it. In the race above, customer 42 ends at version 8. Session A's flush issues its update with where id = 42 and version = 7, matches no rows, and Hibernate raises a stale-state failure - which is exactly what you want: the loser is told, rather than silently winning. Two things it is not. It is not a version check: the bulk statement's own WHERE clause is whatever you wrote, and no row is skipped because its version moved. And it is not portable - JPQL as specified has no versioned form, so this is HQL only. There is also no versioned bulk DELETE; deletion removes the row, so there is nothing left to invalidate. An alternative some people reach for is putting the version in the SET clause by hand, set c.version = c.version + 1. The JPA specification says the update statement should not touch the version attribute directly; Hibernate generally permits it, but the versioned keyword expresses the intent clearly and handles the version type properly, so prefer it. ## Practical guidance Use the versioned form whenever a bulk statement modifies rows that long-running or interactive sessions may be holding - editable master data, workflow status, anything a user has open in a screen. The cost is one extra column in the SET clause and the guarantee that concurrent editors get an honest failure instead of a lost update. Skip it when the affected rows are not held elsewhere - purely batch-owned tables, ingestion staging, denormalised counters - since bumping versions there only creates spurious conflicts for other batch jobs. And remember what remains true regardless: the bulk statement still bypasses the persistence context, so entities already loaded in the current session are stale, including their version snapshot. If you bump versions and then flush a stale instance loaded in the same session, you will hit the stale-state failure yourself. Clear the persistence context after the statement, as with any bulk operation. ## Relationship to the object-layer alternative If conflict detection matters row by row - you want to skip rows that changed rather than invalidate their holders - a bulk statement is the wrong tool: load the entities, mutate them, and let normal versioned writes decide, or express the condition in the bulk statement's WHERE clause explicitly (for example and c.version = :expected) so only untouched rows are affected. The second option is set-based and cheap, but it silently skips rows rather than reporting them, so pair it with the returned row count.
- Does 'update versioned' also verify that no row changed since some earlier read?No. It only adds the version increment to the SET clause. The statement affects every row matching your WHERE clause regardless of its version, so it cannot report a conflict. Its purpose is to invalidate copies other sessions are holding, not to protect the bulk statement itself.
- Is there a versioned form of a bulk DELETE, and if not, what is the concurrency consequence?There is none, because deleting the row leaves no version to increment. A session holding a copy of a deleted row discovers the problem only when it flushes an update that matches zero rows, which Hibernate reports as a stale-state failure. Applications that delete rows users may have open should account for that failure path explicitly.
saying these in an interview costs you the question
- Claiming the versioned keyword performs an optimistic check on the affected rows
- Assuming a plain bulk UPDATE bumps the version because entity updates do
- Expecting update versioned to work in another JPA provider - it is HQL only
- Looking for a versioned bulk DELETE
- Bumping versions in a batch-owned table and then being surprised by conflicts between batch jobs