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

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

Песимістичне блокування блокує рядки в базі даних на рівні СУБД, запобігаючи конкурентному доступу. Інші транзакції не можуть читати або писати заблоковані дані до завершення по...

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

Огляд

Песимістичне блокування блокує рядки в базі даних на рівні СУБД, запобігаючи конкурентному доступу. Інші транзакції не можуть читати або писати заблоковані дані до завершення поточної транзакції.


🟢 Junior Level

Що таке песимістичне блокування

Песимістичне блокування — блокує рядок в БД на рівні СУБД. Інші транзакції не можуть читати/писати заблоковані дані.

// Блокування на запис — інші не можуть читати/писати
Order order = entityManager.find(Order.class, 1L,
    LockModeType.PESSIMISTIC_WRITE);

// Блокування на читання — інші можуть читати, але не писати
Order order = entityManager.find(Order.class, 1L,
    LockModeType.PESSIMISTIC_READ);

Коли використовувати

  • Критичні дані, які часто оновлюються
  • Коли конфлікти часті (оптимістичне блокування не підходить)
  • Фінансові операції, інвентар, бронювання

🟡 Middle Level

LockModeType

PESSIMISTIC_READ:
- SELECT ... FOR SHARE (PostgreSQL)
- Інші транзакції можуть читати, але не можуть писати

PESSIMISTIC_WRITE:
- SELECT ... FOR UPDATE
- Інші транзакції не можуть читати або писати

PESSIMISTIC_FORCE_INCREMENT:
- UPDATE + збільшення version
- Комбінація песимістичного блокування та optimistic versioning

Timeout

// Таймаут 5 секунд — чекати максимум 5 секунд
entityManager.find(Order.class, id,
    LockModeType.PESSIMISTIC_WRITE,
    Map.of("jakarta.persistence.lock.timeout", 5000));

// NOWAIT — не чекати взагалі
entityManager.find(Order.class, id,
    LockModeType.PESSIMISTIC_WRITE,
    Map.of("jakarta.persistence.lock.timeout", 0));

Deadlock

Транзакція 1: блокує A → чекає B
Транзакція 2: блокує B → чекає A
Deadlock! → одна транзакція буде відкатана БД

// Обробка deadlock
try {
    Order order = entityManager.find(Order.class, id,
        LockModeType.PESSIMISTIC_WRITE);
} catch (PessimisticLockException | LockAcquisitionException e) {
    // retry або обробка помилки
}

🔴 Senior Level

Внутрішня реалізація

PostgreSQL: SELECT ... FOR UPDATE
MySQL:      SELECT ... FOR UPDATE
Oracle:     SELECT ... FOR UPDATE NOWAIT / WAIT n
SQL Server: SELECT ... WITH (UPDLOCK, ROWLOCK)

Hibernate генерує правильний SQL для кожної СУБД.

NOWAIT та SKIP LOCKED

// NOWAIT — одразу помилка якщо заблоковано
Map<String, Object> nowait = Map.of(
    "jakarta.persistence.lock.timeout", 0
);

// SKIP LOCKED — пропустити заблоковані рядки (Hibernate 6+)
// Корисно для черг задач

Production патерни

// Pattern 1: Бронювання з блокуванням
@Transactional
public boolean reserveItem(Long itemId, int quantity) {
    Item item = entityManager.find(Item.class, itemId,
        LockModeType.PESSIMISTIC_WRITE);

    if (item.getStock() >= quantity) {
        item.setStock(item.getStock() - quantity);
        return true;
    }
    return false;
}

Best Practices

✅ Короткі транзакції з блокуванням
✅ Timeout для запобігання довгому очікуванню
✅ Індекси для locked columns
✅ Retry при deadlock
✅ SKIP LOCKED для черг

❌ Довгі транзакції з блокуванням
❌ Без timeout
❌ Без індексації locked columns
❌ Блокування без необхідності

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • Песимістичне блокування — SELECT … FOR UPDATE на рівні СУБД
  • LockModeType: PESSIMISTIC_READ (FOR SHARE), PESSIMISTIC_WRITE (FOR UPDATE)
  • Timeout: jakarta.persistence.lock.timeout (0 = NOWAIT, інакше мілісекунди)
  • Deadlock можливий — одна транзакція буде відкатана БД
  • SKIP LOCKED (Hibernate 6+) — пропустити заблоковані рядки, корисно для черг
  • Короткі транзакції — інші транзакції чекають завершення блокування

Пов’язані теми:

  • [[17. Як реалізувати оптимістичне блокування в JPA]]
  • [[19. Що таке @Version і навіщо вона потрібна]]
  • [[15. Що робить метод refresh()]]
  • [[13. Як працює механізм flush в Hibernate]]