Что происходит при конфликте версий в механизме оптимистической блокировки JPA/Hibernate?

«Что происходит при конфликте версий в механизме оптимистической блокировки JPA/Hibernate?» — вопрос из категории Hibernate, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

При оптимистической блокировке каждый объект сущности имеет поле-версию (аннотированное @Version). При попытке обновления или удаления Hibernate проверяет, не изменилась ли эта версия в базе данных с момента загрузки сущности в persistence context.

Если версии не совпадают (конфликт), возникает исключение OptimisticLockException (или его подкласс StaleObjectStateException). Это указывает, что другая транзакция уже изменила данные.

Пример сущности:

@Entity
public class Account {
    @Id
    private Long id;
    private BigDecimal balance;

    @Version
    private Long version; // Поле для оптимистической блокировки
}

Что происходит при конфликте:

  1. Транзакция A загружает Account с version = 1.
  2. Транзакция B загружает тот же Account с version = 1.
  3. Транзакция B изменяет баланс и фиксирует изменения. Hibernate увеличивает version до 2 в БД.
  4. Транзакция A пытается зафиксировать свои изменения. Hibernate выполняет запрос вида:
    UPDATE account SET balance=?, version=2 WHERE id=? AND version=1;
  5. Количество обновленных строк будет 0 (так как version уже не равен 1). Hibernate обнаруживает это и выбрасывает OptimisticLockException.

Стратегии обработки:

  • Информирование пользователя: Поймать исключение, откатить транзакцию и сообщить пользователю, что данные были изменены, предложив повторить операцию.
  • Автоматическая повторная попытка: Реализовать механизм retry с повторной загрузкой актуальных данных.

Это предотвращает «потерянные обновления» (lost updates) без использования пессимистических блокировок на уровне БД.