Два инстанса списывают бонусы: почему баланс сходится неверно
Списание бонусов реализовано так (уровень изоляции по умолчанию, READ COMMITTED):
@Transactional
public void withdraw(Long accountId, int amount) {
Account acc = repository.findById(accountId).orElseThrow();
acc.setBalance(acc.getBalance() - amount);
repository.save(acc);
}
При балансе 100 два инстанса сервиса одновременно списали по 30. В базе осталось 70 вместо 40. Что произошло и как это чинить?
- Проблема в кэше первого уровня Hibernate: достаточно вызвать flush перед сохранением, и запись пойдёт в базу сразу
- Потерянное обновление: оба прочитали 100 и записали 70. Нужен UPDATE по текущему значению колонки, блокировка строки или версионирование
- Достаточно пометить метод synchronized: тогда списания выстроятся в очередь и баланс сойдётся
- @Transactional уже защищает от такого, значит в коде есть повторный вызов withdraw для одной операции
