Lesson 20 / 25
Optimistic and Pessimistic Locking
Prevent lost updates with @Version and choose pessimistic locks when needed.
Concurrent edits without lost updates
Two users open the same product, change the price and save; without protection, the second save silently overwrites the first: a lost update. Optimistic locking solves this with a version attribute annotated @Version (an integer or timestamp). Every update includes where id = ? and version = ? and increments the version; if another transaction changed the row meanwhile, zero rows match and Hibernate throws an OptimisticLockException (wrapped by Spring as ObjectOptimisticLockingFailureException). The application then retries or shows a "someone else changed this" message. Optimistic locking takes no database locks, scales well and suits most web applications where conflicts are rare. Pessimistic locking (LockModeType.PESSIMISTIC_WRITE, translated to SELECT ... FOR UPDATE) locks rows when reading, making other writers wait; use it for short, high-contention critical sections such as decrementing inventory or allocating seats, with a lock timeout to avoid long waits. Also expose the version to clients in APIs (for example as an ETag) so edits based on stale data are detected across requests.
@Version for optimistic locking and a pessimistic lock for stock
Conflicts become exceptions instead of silent overwrites.
@Entity
public class Product {
@Id @GeneratedValue private Long id;
private BigDecimal price;
private int stock;
@Version
private long version; // UPDATE ... WHERE id = ? AND version = ?
}
@Transactional
public void changePrice(Long id, BigDecimal newPrice, long expectedVersion) {
Product p = em.find(Product.class, id);
if (p.getVersion() != expectedVersion) throw new StaleEditException(id); // client edited old data
p.changePrice(newPrice); // concurrent commit -> OptimisticLockException at flush
}
public interface ProductRepository extends JpaRepository<Product, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE) // SELECT ... FOR UPDATE
@QueryHints(@QueryHint(name = "jakarta.persistence.lock.timeout", value = "2000"))
@Query("select p from Product p where p.id = :id")
Optional<Product> findForStockUpdate(Long id);
}
@Transactional
public void reserve(Long productId, int qty) {
Product p = productRepository.findForStockUpdate(productId).orElseThrow();
if (p.getStock() < qty) throw new OutOfStockException(productId);
p.decreaseStock(qty);
}Editing a shared document
Optimistic locking is everyone editing freely and the system warning "this file changed since you opened it" when you save. Pessimistic locking is checking the file out so nobody else can edit until you return it.
Quick check: What does a @Version attribute enable?
- Automatic database backups
- Faster inserts
- Second-level caching
- Optimistic locking that detects concurrent modifications and prevents lost updates
Answer
Optimistic locking that detects concurrent modifications and prevents lost updates — Version checks in UPDATE statements detect conflicting concurrent changes.