Ответ
Optimistic Locking — это стратегия управления конкурентным доступом к данным, которая предполагает, что конфликты при одновременном обновлении редки. Вместо физической блокировки записи на уровне БД используется контроль версий.
Реализация через @Version:
@Entity
public class Account {
@Id
private Long id;
private BigDecimal balance;
@Version // Ключевая аннотация
private Long version; // Поле для контроля версии
}
Как работает:
- Чтение: При загрузке сущности JPA сохраняет значение поля
version. - Обновление: При сохранении (
merge/flush) JPA добавляет вWHERE-условие проверку на текущую версию:UPDATE account SET balance=?, version=? WHERE id=? AND version=?; - Конфликт: Если количество обновлённых строк = 0 (значит, версия изменилась другим транзакцией), JPA выбрасывает
OptimisticLockException.
Обработка конфликта:
@Transactional
public void transfer(Long accountId, BigDecimal amount) {
try {
Account acc = accountRepository.findById(accountId).orElseThrow();
acc.withdraw(amount);
accountRepository.save(acc);
} catch (OptimisticLockException e) {
// Стратегия повтора (retry) или уведомление пользователя
throw new ConcurrentModificationException("Данные были изменены. Попробуйте снова.");
}
}
| Плюсы и минусы: | Плюсы | Минусы |
|---|---|---|
| Высокая производительность (нет долгих блокировок) | Требует обработки исключений на уровне приложения | |
| Масштабируемость | Не подходит для сценариев с частыми конфликтами (например, инвентаризация последнего товара) | |
| Избегает взаимных блокировок (deadlocks) |
Альтернатива: Pessimistic Locking (LockModeType.PESSIMISTIC_WRITE), который блокирует запись на уровне БД.
Ответ 18+ 🔞
Да ты посмотри, что вытворяют эти программисты, блядь! Оптимистичная блокировка, говоришь? Ну прям как в жизни — надеемся на лучшее, а в итоге получаем по ебалу исключением!
Вот смотри, как они это выкручивают. Вместо того чтобы наглухо запирать запись в базе, как сторож-алкаш сарай на ночь, они просто ставят на неё версию, как на бутылке с самогоном. Прикол в том, что все думают, будто конфликтов не будет. Ну, как в очереди за колбасой в девяностые — все вежливые, пока не кончится последняя палка.
Вот как это в коде выглядит, не трогай:
@Entity
public class Account {
@Id
private Long id;
private BigDecimal balance;
@Version // Вот эта, блядь, волшебная палочка!
private Long version; // Счётчик, который всех и спасает, и подставляет
}
А работает это, блядь, так:
- Читаем: Загрузил ты себе запись, и версию эту, сука, запомнил. Как номер билета в кассе.
- Пишем: Пошёл обновлять, а JPA тебе тихонько в запрос подсунула проверку:
UPDATE account SET balance=?, version=? WHERE id=? AND version=?;То есть, типа, "обновлю только если версия та же, что и брал". Хитро, блядь!
- Пиздец: А если строк обновлённых ноль — значит, пока ты тут умничал, кто-то уже успел твою запись подправить и версию увеличить. И тебе в ответ — жирный
OptimisticLockExceptionпрямо в ебальник!
Ловить это безобразие надо как-то так:
@Transactional
public void transfer(Long accountId, BigDecimal amount) {
try {
Account acc = accountRepository.findById(accountId).orElseThrow();
acc.withdraw(amount);
accountRepository.save(acc);
} catch (OptimisticLockException e) {
// Вот тут либо по-умному — перепробовать, либо просто пользователю сказать, что всё просрал
throw new ConcurrentModificationException("Данные были изменены. Попробуйте снова.");
}
}
Ну и как всегда, есть свои плюсы и минусы, ёпта:
| Что хорошего | Что за дерьмо |
|---|---|
| Быстро, блокировок долгих нет | Надо эти ебучие исключения ловить самому |
| Масштабируется легко | Если все лезут в одну запись — будет пиздец и сплошные повторы |
| Deadlock'ов не будет (в теории) | Для вещей типа "последний товар на складе" — вообще не катит |
А есть же ещё, блядь, и пессимистичный подход! Тот самый PESSIMISTIC_WRITE. Это когда ты сходу хватаешь запись, вешаешь на неё замок на уровне базы и не пускаешь никого, пока сам не отойдёшь. Как собака на сене, только с SQL-запросом. Надёжно, но медленно и можно всех заблокировать. Выбирай, что тебе больше по душе — верить в хорошее и получать по щам, или никому не верить и сидеть на замке самому.