Як реалізувати оптимістичне блокування в JPA
Оптимістичне блокування — стратегія управління конкурентним доступом, яка дозволяє кільком транзакціям читати одні й ті самі дані, але запобігає конфліктуючим оновленням через п...
Огляд
Оптимістичне блокування — стратегія управління конкурентним доступом, яка дозволяє кільком транзакціям читати одні й ті самі дані, але запобігає конфліктуючим оновленням через перевірку версії.
🟢 Junior Level
Що таке оптимістичне блокування
Оптимістичне блокування дозволяє кільком транзакціям читати одні й ті самі дані, але при оновленні перевіряє, що дані не були змінені іншою транзакцією.
Реалізується через анотацію @Version:
@Entity
public class Order {
@Id
private Long id;
@Version
private Integer version; // Hibernate автоматично збільшує
private String status;
}
Як працює
// Транзакція 1
Order order1 = em.find(Order.class, 1L); // version = 1
// Транзакція 2
Order order2 = em.find(Order.class, 1L); // version = 1
order2.setStatus("shipped");
em.flush(); // version = 2, UPDATE WHERE version = 1
// Транзакція 1
order1.setStatus("cancelled");
em.flush(); // ❌ OptimisticLockException!
// UPDATE WHERE version = 1 → 0 rows updated → виняток
Чому “оптимістична”
Тому що ми оптимістично припускаємо, що конфліктів не буде, і не блокуємо дані на читання. Конфлікт виявляється тільки при записі.
🟡 Middle Level
LockModeType для оптимістичного блокування
// OPTIMISTIC — перевірка version при commit/flush
em.lock(order, LockModeType.OPTIMISTIC);
// OPTIMISTIC_FORCE_INCREMENT — завжди збільшує version
em.lock(order, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
// Навіть якщо order не змінювався, version збільшиться
Обробка OptimisticLockException
try {
order.setStatus("cancelled");
entityManager.flush();
} catch (OptimisticLockException e) {
// Оновити дані з БД і повторити
entityManager.refresh(order);
// retry logic
order.setStatus("cancelled");
entityManager.flush();
}
Retry з Spring
@Retryable(value = OptimisticLockException.class, maxAttempts = 3)
@Transactional
public Order updateOrder(OrderDto dto) {
Order order = entityManager.find(Order.class, dto.id());
order.setStatus(dto.status());
return order;
}
🔴 Senior Level
Внутрішня реалізація
@Version → version column в БД
При UPDATE:
UPDATE orders SET status = ?, version = version + 1
WHERE id = ? AND version = ?
Якщо 0 rows updated → OptimisticLockException
Типи полів version
@Version
private Integer version; // int — автоматично збільшується
@Version
private Long version; // long — автоматично збільшується
@Version
private Timestamp version; // timestamp — оновлюється автоматично
Оптимістична vs песимістична
| Оптимістична | Песимістична | |
|---|---|---|
| Блокування | Ні (тільки при записі) | Так (при читанні) |
| Конфлікт | Виявляється при записі | Запобігається при читанні |
| Продуктивність | Висока (немає блокувань) | Нижче (блокування) |
| Deadlock | Неможливий | Можливий |
| Коли | Рідкі конфлікти | Часті конфлікти |
Best Practices
✅ @Version на всіх mutable сутностях
✅ Retry при OptimisticLockException
✅ OPTIMISTIC_FORCE_INCREMENT коли потрібно
✅ @Retryable для автоматичного retry
✅ Моніторинг частоти конфліктів
❌ Без @Version для важливих даних
❌ Без retry логіки
❌ Ручна зміна version
❌ Песимістичне блокування без причини
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- Оптимістичне блокування через @Version — автоматично збільшує version при кожному UPDATE
- WHERE clause включає version check: WHERE id = ? AND version = ?
- При конфлікті (0 rows updated) → OptimisticLockException
- LockModeType: OPTIMISTIC (перевірка version), OPTIMISTIC_FORCE_INCREMENT (завжди збільшує)
- Типи version: Integer, Long (збільшуються автоматично), Timestamp (небезпечний)
Пов’язані теми:
- [[18. Як реалізувати песимістичне блокування в JPA]]
- [[19. Що таке @Version і навіщо вона потрібна]]
- [[13. Як працює механізм flush в Hibernate]]
- [[15. Що робить метод refresh()]]