skip to content

JPA's jakarta.persistence.lock.scope hint accepts PessimisticLockScope.NORMAL or PessimisticLockScope.EXTENDED. What extra rows does EXTENDED lock, and what does it still leave unprotected?

level: seniorimportance: nice to knowfreq 18%

answer

  1. NORMAL = entity rows: primary + secondary + joined-inheritance parents
  2. EXTENDED = plus element-collection and join tables
  3. freezes membership, not the associated entities
  4. hint name: jakarta.persistence.lock.scope
  5. provider/dialect support is uneven — check the SQL

basics

~20 s

NORMAL locks the rows that make up the entity itself, including joined-inheritance and secondary-table rows. EXTENDED additionally locks the rows of join tables and element-collection tables the entity owns, so nobody can add or remove elements. It never locks the associated entities' own rows.

solid answer

~50 s

`PessimisticLockScope.NORMAL` — the default — locks the rows that constitute the entity: its own table row, plus the rows in a `@SecondaryTable` or in the parent tables of a joined-inheritance hierarchy, since those together *are* the entity's state. `EXTENDED` adds the rows in tables the entity owns relationally but that are not part of it: `@ElementCollection` tables and the join tables of `@OneToMany`/`@ManyToMany` associations. The point is membership stability — with `EXTENDED` another transaction cannot insert or delete a link row, so the collection's contents cannot change while you hold the lock. What it does not do is lock the entities on the other side. If `Order` extends its lock over the `order_tag` join table, another transaction can still update the `Tag` rows themselves; only the *association* is frozen. Support is provider- and dialect-dependent, so confirm against the emitted SQL rather than assuming.

go deeper

for a junior

Just know the default lock covers only the entity's own rows, not its collections.

for a middle

Name the two scopes and say EXTENDED adds join tables and element-collection tables so membership cannot change.

for a senior

Stress that it does not lock the association targets, that support varies by provider and dialect, and that a unique constraint is often a cheaper answer.

for a principal

Weigh extended scope against redesigning the invariant — constraint enforcement or a single serialisation point — given the contention it adds to the busiest tables in the schema.

## What "scope" means When you ask for a pessimistic lock you name an entity, but an entity is not always one row and rarely owns only one table. The scope hint tells the provider how far outwards from that entity the row locks should reach. ## NORMAL — the entity's own rows The default. It locks the rows that hold the entity's state: - its primary table row; - the corresponding rows in any `@SecondaryTable`; - for `InheritanceType.JOINED`, the rows in the superclass tables, because the entity's state is physically split across them. Relationship tables are excluded. Concretely, locking an `Order` with `NORMAL` stops anyone from changing the order's own columns, but does not stop them adding a row to an `order_tag` join table — so the order's tag collection can change under you even though the lock "succeeded". ## EXTENDED — plus the owned relationship rows `EXTENDED` additionally locks the rows in tables that exist only because of this entity's mappings: - `@ElementCollection` tables (the entity's embedded value collections); - join tables of `@ManyToMany` and unidirectional `@OneToMany` mappings owned by this entity. The guarantee it buys is *membership stability*: for the duration of your transaction nobody can insert or delete a link row, so the collection you loaded still has the same members at commit. That matters when your invariant is about the set — "a user may hold at most five active roles", "a basket may not contain two mutually exclusive items". With `NORMAL` such a check races: two transactions each read the collection, each add the row that keeps the count legal on its own, and both commit. ## What EXTENDED explicitly does not do It is a lock on the *association*, not on the associated data. Locking `Order` with `EXTENDED` freezes membership in `order_tag`, but any transaction may still update or even delete the `Tag` rows that membership points to. If you also need those, you must lock them yourself — for example by loading the association targets with an explicit lock mode. Nor does the extended scope cascade transitively; it is one level, over tables the locked entity owns. ## Practical cautions The hint is defined by JPA but its support is genuinely uneven across providers, versions and dialects — a scope a provider cannot honour may simply be ignored. Because this fails silently and looks like it worked, verify against the emitted SQL on the database you deploy on before you rely on it for an invariant. There is also a cost. The extended scope multiplies the rows you hold, and join tables are usually the hottest tables in a schema, so lock waits and deadlock probability rise noticeably. In many designs a cheaper and more portable answer to a collection-membership invariant is a unique constraint on the join table, or locking a single designated parent row so all writers to the aggregate serialise on it. In interviews this is a differentiator question: knowing that a lock on an entity says nothing about its join tables by default is the insight being tested.

  • You lock a parent entity with the default scope and still see a child appear in its collection. Why?
    Because the default scope covers only the rows that make up the entity itself. A new element arrives as an insert into a join table or a foreign key update on the child table, neither of which your lock covers. Either request the extended scope, lock the child side explicitly, or enforce the rule with a constraint.
  • Does the extended scope protect the entities on the far side of a many-to-many association?
    No. It locks the link rows in the join table, so membership cannot change, but the target entities' own rows are untouched and another transaction may update or delete them. If their state matters to your invariant, load them with an explicit lock mode as well.

saying these in an interview costs you the question

  • Assuming a lock on an entity automatically covers its collections and associated entities.
  • Believing the extended scope cascades through the whole object graph.
  • Relying on the scope hint without verifying the provider and dialect actually honour it.
  • Reaching for the extended scope on hot join tables without accounting for the added contention and deadlock risk.

context