Питання 17 · Розділ 16

Як реалізувати оптимістичне блокування в JPA

Оптимістичне блокування — стратегія управління конкурентним доступом, яка дозволяє кільком транзакціям читати одні й ті самі дані, але запобігає конфліктуючим оновленням через п...

Мовні версії: English Russian Ukrainian

Огляд

Оптимістичне блокування — стратегія управління конкурентним доступом, яка дозволяє кільком транзакціям читати одні й ті самі дані, але запобігає конфліктуючим оновленням через перевірку версії.


🟢 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()]]