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
UPDATEchecks 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.
- The SQL
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
}
@Versiontells JPA/Hibernate to use this field for optimistic locking.- Hibernate will automatically:
- Include
versioninWHEREclause duringUPDATE - Increment
versionafter successful update.
- Include
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:
| id | name | quantity | version |
|---|---|---|---|
| 1 | Laptop | 10 | 1 |
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
@EnableTransactionManagementis 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
| Feature | Optimistic Locking | Pessimistic Locking |
|---|---|---|
| Locking mechanism | No database locks | Locks rows until commit |
| Performance | High (rare conflicts) | Lower (blocking) |
| Conflict handling | At commit (exception) | During transaction |
| Deadlock risk | Low | High |
