Learnitweb

Optimistic locking using Spring Boot and JPA Hibernate

Optimistic locking is a concurrency control strategy used in applications (including Spring Boot + JPA/Hibernate) to prevent lost updates when multiple transactions try to update the same record without using database-level locks. Unlike pessimistic locking, it assumes conflicts are rare and checks for conflicts only at the time of commit.

Here’s a detailed explanation:

1. Concept of Optimistic Locking

  • Each record in the database has a version field (usually a number or timestamp).
  • When reading a record:
    • The application retrieves the version.
  • When updating the record:
    • The SQL UPDATE checks that the version hasn’t changed since it was read.
    • If the version is unchanged, the update proceeds, and the version is incremented.
    • If the version has changed, it means another transaction has updated the record, and the update fails with an optimistic lock exception.

Key points:

  • Prevents lost updates without locking the row.
  • Good for applications where write conflicts are rare.
  • Avoids blocking other transactions, unlike pessimistic locking.

2. JPA/Hibernate Implementation

Step 1: Add a Version Field

import jakarta.persistence.*;

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int quantity;

    @Version
    private Long version; // required for optimistic locking

    // getters and setters
}
  • @Version tells JPA/Hibernate to use this field for optimistic locking.
  • Hibernate will automatically:
    • Include version in WHERE clause during UPDATE
    • Increment version after successful update.

Step 2: Update Entity Normally

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    public void updateQuantity(Long productId, int newQuantity) {
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new RuntimeException("Product not found"));

        product.setQuantity(newQuantity);
        productRepository.save(product); // version is checked automatically
    }
}
  • If another transaction has updated the same product after it was read, Hibernate throws: javax.persistence.OptimisticLockException

Step 3: Handle OptimisticLockException

try {
    productService.updateQuantity(1L, 50);
} catch (OptimisticLockException e) {
    System.out.println("Update conflict detected. Please retry.");
}
  • Typically, you retry the transaction or notify the user about the conflict.

3. SQL Example

Assume initial record:

idnamequantityversion
1Laptop101

Two users read the record simultaneously:

  • User A sets quantity=15
  • User B sets quantity=20

Optimistic locking ensures that only the first commit succeeds:

UPDATE product
SET quantity = 15, version = version + 1
WHERE id = 1 AND version = 1; -- succeeds
UPDATE product
SET quantity = 20, version = version + 1
WHERE id = 1 AND version = 1; -- fails, version mismatch

4. When to Use Optimistic Locking

  • High read, low write scenarios (e.g., e-commerce inventory updates, banking transactions)
  • Avoids deadlocks
  • Best when conflicts are rare

5. Spring Boot Configuration Tips

  • Make sure @EnableTransactionManagement is enabled (default in Spring Boot).
  • Annotate service methods with @Transactional.
  • Retry logic can be implemented if updates fail due to optimistic lock exceptions.

6. Advantages vs Pessimistic Locking

FeatureOptimistic LockingPessimistic Locking
Locking mechanismNo database locksLocks rows until commit
PerformanceHigh (rare conflicts)Lower (blocking)
Conflict handlingAt commit (exception)During transaction
Deadlock riskLowHigh