TGViewer
Разработка с Дарьей Матвеевой Разработка с Дарьей Матвеевой @system_design_explained · 72 subscribers
Post #68 104
Недавно занималась задачей, где нужно было реализовать оптимистическую блокировку сущности Hibernate при конкурентном доступе двух методов.

Задача была такая:
Сервис А обрабатывает таски. Во время обработки он делает запрос в сервис B. Бывает, что сервис B отвечает слишком медленно, и в таком случае нам хотелось бы, не дожидаясь ответа, отменять задачу и создавать другую с высоким приоритетом. Для этого я реализовала работающий по расписанию метод, чтобы он извлекал из БД и обрабатывал все задачи с истекшим временем ожидания.
Псевдокод:
// когда штатно пришел ответ от B
@Transactional
public void processTask(long id) {
TaskEntity task = taskRepository.findById(id);
if (task.getStatus() != IN_PROGRESS) {
return;
}
// some logic
task.setStatus(DONE);
taskRepository.save(task);
}

// когда время ожидания истекло
@Transactional
public void processLongAwaitingTask(long id) {
TaskEntity task = taskRepository.findById(id);
if (task.getStatus() != IN_PROGRESS) {
return;
}
task.setStatus(EXPIRED);
taskRepository.save(task);
PriorityTaskEntity priorityTask = new PriorityTaskEntity(...);
priorityTaskRepository.save(priorityTask);
}


В обоих случаях мы проверяем, что статус задачи корректный, и только потом продолжаем обработку. Но это не исключает ситуацию, когда одновременно пришел ответ от сервиса B, и истекло время ожидания задачи. И оба метода начали выполняться одновременно, и пока ни одна из транзакций не успела закоммитить изменение статуса. Теоретически можно получить вариант, когда наша задача в статусе DONE, а мы все равно создали PriorityTask, что нарушит консистентность данных. Чтобы решить проблему, можно использовать оптимистическую блокировку TaskEntity.

Для этого нужен атрибут version в таблице tasks в БД, и, соответственно, поле в сущности TaskEntity. Hibernate через это поле отслеживает изменения сущности. При сохранении он автоматически инкрементирует поле version, и выбрасывает OptimisticLockException, если поле в БД не совпадает с ожидаемым.
Поэтому, если транзакция из метода processTask закоммитит изменения первой, то мы получим исключение в методе processLongAwaitingTask, и его транзакция будет откачена. А если транзакция из processLongAwaitingTask закоммитит первой, то соответственно откатятся изменения processTask. Так сохранится консистентность данных.

Теоретически, есть другой вариант — использовать пессимистическую блокировку.
В этом случае только одна транзакция сможет получить доступ к строке из таблицы task, а другая будет ждать, пока первая не освободит данные. Пессимистические блокировки используются в условиях высокой вероятности конфликтов и сильных требований к консистентности данных, и могут замедлять процессинг и приводить к дедлокам. Но это не наш, случай, т.к. мы ожидаем, что конфликты будут редкими.
  • 👍 1
  • 🔥 1
More from @system_design_explained
  1. Sep 22, 2026Нашла способ, который мгновенно и без дополнительных усилий увеличил мою продуктивность пр…
  2. Mar 27, 2026Я в ВК : https://vk.ru/dev_with_dm
  3. Mar 27, 2026Читаю в последнее время много критики микросервисов, и у меня тоже есть пример, как раз за…
  4. Mar 4, 2026В функциональных языках рекомендуется делать все объекты immutable, и, хотя на работе я пи…
  5. Nov 21, 2025Обнаружила еще один плюс TDD. Согласно подходу, я пишу тест, который падает, пишу код, что…
  6. Nov 13, 2025Недавно столкнулась с ситуацией — один из сервисов на тестовом стенде не вычитывал сообщен…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →