Ответ
Для предотвращения потери обновления (lost update), когда две транзакции читают, модифицируют и перезаписывают одну и ту же строку, требуется уровень изоляции, обеспечивающий блокировки на запись.
Подходящие уровни:
REPEATABLE_READ(в большинстве СУБД, кроме Oracle): На практике часто реализуется с помощью блокировок строк на запись. Если две транзакции попытаются изменить одну строку, вторая будет ждать или завершится с ошибкой.SERIALIZABLE: Гарантированно предотвращает потерю обновления за счёт строжайших блокировок или механизмов управления параллелизмом с проверкой версий (MVCC).
Пример сценария потери обновления и его предотвращение:
// Безопасный код с REPEATABLE_READ
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void incrementCounter(Long counterId) {
Counter counter = counterRepository.findById(counterId).orElseThrow();
// На строку 'counter' установлена блокировка для чтения (в REPEATABLE_READ),
// которая будет повышена до блокировки на запись при сохранении.
counter.setValue(counter.getValue() + 1);
counterRepository.save(counter);
// Вторая параллельная транзакция, пытающаяся изменить эту же строку,
// будет заблокирована до коммита этой транзакции.
}
Примечание: В READ_COMMITTED потеря обновления возможна, если не используется явная блокировка (например, SELECT ... FOR UPDATE).