Ответ
Оптимистическая блокировка в Spring Data JPA реализуется через механизм версионирования сущностей. Она предотвращает "потерю обновлений" (lost update) в многопользовательских сценариях без физических блокировок в БД.
1. Базовая реализация:
Добавьте поле с аннотацией @Version в сущность.
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private BigDecimal balance;
@Version // Ключевая аннотация
private Long version; // Тип может быть Long, Integer, Timestamp
// Геттеры и сеттеры
}
2. Как это работает:
- При загрузке сущности JPA фиксирует значение
version. - При обновлении в
UPDATE-запрос добавляется условиеWHERE id=? AND version=?. - Если версия в БД изменилась (обновил другой поток/транзакция), количество обновлённых строк будет 0.
- JPA выбрасывает
OptimisticLockException(обёрнутое вJpaSystemExceptionилиObjectOptimisticLockingFailureExceptionв Spring).
3. Пример использования и обработки конфликта:
@Service
public class AccountService {
@Transactional
public void transferMoney(Long accountId, BigDecimal amount) {
try {
Account account = accountRepository.findById(accountId).orElseThrow();
account.setBalance(account.getBalance().add(amount));
// При вызове save() выполнится проверка версии
accountRepository.save(account);
} catch (ObjectOptimisticLockingFailureException ex) {
// Стратегия обработки: повторить операцию, уведомить пользователя
throw new RetryableException("Конфликт версий. Попробуйте ещё раз.", ex);
}
}
}
Ключевые моменты:
- Тип поля: Должен быть
Long,Integer,java.sql.Timestampилиjava.time.Instant. - Изменение: Поле обновляется автоматически JPA, не меняйте его вручную.
- Когда использовать: Идеально для read-heavy сценариев с редкими конфликтующими обновлениями.
- Альтернатива: Для write-heavy сценариев с частыми конфликтами рассмотрите пессимистическую блокировку (
@Lock(LockModeType.PESSIMISTIC_WRITE)).