A PESSIMISTIC_WRITE query is useful only when the read and mutation share the transaction that holds the database lock.
Spring Data JPA pessimistic lock: hold it only through the stock decision
Make the lock cover the decision
A stock allocator reads one DepotSlot, checks that 47 units remain, then subtracts 12. Without a concurrency control rule, two requests may both see 47. A repository query with PESSIMISTIC_WRITE can serialize that decision for the row, provided the service method is transactional and the database honors the lock mode. Returning the entity and deciding later, after commit, loses the protection.
Keep external work out
Do not call a payment service or broker while holding the row lock. Decide, persist, and commit; then use a transactional outbox for delivery. A lock wait can consume a pooled connection and request thread. Compare this path with optimistic locking when contention is rare.
Test the real database
Launch two transactions against the same slot and prove the accepted quantity never exceeds the starting balance. Include a timeout path and an independent tenant ID. The behavior of lock hints, isolation and timeout differs by database; an H2-only check is insufficient for a deployment claim. The code is an implementation sketch.
Implementation sketch
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select s from DepotSlot s where s.tenantId = :tenantId " +
"and s.sku = :sku")
Optional<DepotSlot> lockSlot(String tenantId, String sku);
@Transactional
public void allocate(String tenantId, String sku, int units) {
DepotSlot slot = slots.lockSlot(tenantId, sku).orElseThrow();
slot.reserve(units);
}Cost and verification
A row lock trades conflict retries for waiting and possible deadlocks. It can lower throughput on a hot SKU, so bound the transaction and record lock-wait latency.
Common Mistakes
- Do not assume the lock survives after the repository transaction returns.
- Do not hold the lock across remote calls.
- Do not treat an in-memory database concurrency test as proof for the deployed database.
Read next
Spring JPA optimistic locking: reject a stale stock update, Spring transaction isolation: state the anomaly you need to prevent, Spring outbox transaction boundaries: where atomicity ends, Spring Data JPA bulk update: the managed entity can still hold the old value.
