Як реалізувати песимістичне блокування в JPA
Песимістичне блокування блокує рядки в базі даних на рівні СУБД, запобігаючи конкурентному доступу. Інші транзакції не можуть читати або писати заблоковані дані до завершення по...
Огляд
Песимістичне блокування блокує рядки в базі даних на рівні СУБД, запобігаючи конкурентному доступу. Інші транзакції не можуть читати або писати заблоковані дані до завершення поточної транзакції.
🟢 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]]