Companion repo for the tutorial: Optimistic vs Pessimistic Locking in JPA
Demonstrates the lost update problem and both JPA solutions using a Product inventory example with real PostgreSQL via Testcontainers.
mvn verifyDocker must be running. Testcontainers starts a PostgreSQL 16 container automatically.
versionFieldIncrementsOnEachUpdate- the@Versioncolumn starts at 0 and increments on every UPDATEupdateIncludesVersionCheck- Hibernate appendsWHERE version = ?to every UPDATE statementconcurrentModificationThrowsOptimisticLockException- a stale write is rejected withObjectOptimisticLockingFailureExceptioninstead of silently overwriting the earlier change
findByIdWithLockIssuesSelectForUpdate-@Lock(PESSIMISTIC_WRITE)generatesSELECT ... FOR UPDATEconcurrentAccessProducesCorrectStock- two threads each reduce stock by 3 from a starting value of 10; the result is always exactly 4 with no errors
src/main/java/com/umurinan/jpalocking/
├── entity/
│ └── Product.java # @Version field enables optimistic locking
├── repository/
│ └── ProductRepository.java # findByIdWithLock uses @Lock(PESSIMISTIC_WRITE)
├── service/
│ ├── OptimisticInventoryService.java # relies on @Version, retries on conflict
│ └── PessimisticInventoryService.java # uses SELECT FOR UPDATE
└── JpaLockingApplication.java
- Spring Boot 4.0.5
- Java 21
- Spring Data JPA / Hibernate
- PostgreSQL 16 (via Testcontainers)