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

Що таке @Version і навіщо вона потрібна

@Version — анотація JPA для реалізації оптимістичного блокування. Вона автоматично відстежує версію сутності та запобігає конфліктуючим оновленням від кількох транзакцій.

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

Огляд

@Version — анотація JPA для реалізації оптимістичного блокування. Вона автоматично відстежує версію сутності та запобігає конфліктуючим оновленням від кількох транзакцій.


🟢 Junior Level

Що таке @Version

@Version — анотація для оптимістичного блокування. Автоматично відстежує версію сутності та запобігає конфліктуючим оновленням.

@Entity
public class Order {
    @Id
    private Long id;

    @Version
    private Integer version;  // збільшується автоматично

    private String status;
}

Навіщо потрібна

Коли дві транзакції оновлюють один об’єкт, @Version гарантує що одна з них отримає помилку:

Initial: Order(id=1, version=1, status="new")

Транзакція 1: читає (version=1)
Транзакція 2: читає (version=1)
Транзакція 2: оновлює → UPDATE WHERE version=1 → version=2 ✅
Транзакція 1: оновлює → UPDATE WHERE version=1 → 0 rows → ❌ OptimisticLockException

Як це працює — просто

1. При INSERT: version = 0. При кожному UPDATE: version = version + 1.
2. При кожному UPDATE: WHERE clause: WHERE id = ? AND version = ?
3. Якщо version не співпав → 0 rows updated → OptimisticLockException

🟡 Middle Level

Типи полів version

@Version
private Integer version;  // int — збільшується на 1

@Version
private Long version;     // long — збільшується на 1

@Version
private Timestamp version;  // timestamp — оновлюється поточним часом

Типові помилки

// ❌ Ручна зміна version
order.setVersion(0);  // ❌ зламає механізм блокування

// ❌ Немає @Version для важливих даних
@Entity
public class Account {
    // немає @Version → два потоки можуть одночасно змінити баланс
}

// ❌ Ігнорування OptimisticLockException
try {
    em.flush();
} catch (OptimisticLockException e) {
    // мовчки проігнорували → дані втрачено
}

🔴 Senior Level

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

Hibernate:
- При INSERT: version = 0 (або 1 для деяких типів)
- При UPDATE: version = version + 1
- WHERE clause: WHERE id = ? AND version = ?
- Якщо 0 rows → OptimisticLockException

Для Timestamp:
- version = CURRENT_TIMESTAMP
- Timestamp-версія небезпечна: якщо дві транзакції оновлять сутність в одну мілісекунду, conflict НЕ виявиться. В production використовуйте Integer/Long, а не Timestamp.

OPTIMISTIC_FORCE_INCREMENT

// Збільшити version без зміни entity
em.lock(order, LockModeType.OPTIMISTIC_FORCE_INCREMENT);

// Коли потрібно:
// - При зміні дочірньої колекції
// - Для примусової інвалідації кешу

Performance implications

@Version overhead:
- +1 column в таблиці
- +1 condition в WHERE при UPDATE
- Практично negligible

Benefits:
- Запобігання lost updates
- Немає блокувань (на відміну від pessimistic)
- Автоматичне управління

Best Practices

✅ @Version на всіх mutable сутностях
✅ Retry при OptimisticLockException
✅ Не змінювати version вручну
✅ Моніторинг частоти конфліктів

❌ Без @Version для критичних даних
❌ Ручне управління version
❌ Ігнорування OptimisticLockException

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

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

  • @Version — анотація для оптимістичного блокування, автоматично збільшує version при UPDATE
  • WHERE clause: WHERE id = ? AND version = ? — якщо 0 rows → OptimisticLockException
  • Типи: Integer/Long (збільшуються на 1), Timestamp (оновлюється поточним часом, небезпечний)
  • OPTIMISTIC_FORCE_INCREMENT — збільшує version без зміни entity
  • @Version на всіх mutable сутностях — запобігає lost updates

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

  • [[17. Як реалізувати оптимістичне блокування в JPA]]
  • [[18. Як реалізувати песимістичне блокування в JPA]]
  • [[15. Що робить метод refresh()]]
  • [[20. Як працюють каскадні операції (Cascade)]]