TGViewer
Java Mentor Java Mentor @your_java_mentor · 289 subscribers
Post #269 336
🔥 Проблемы изоляции транзакций: как это выглядит в жизни и коде 🚀

Обещал рассказать про сложности, с которыми я столкнулся на работе.
Долго думал, как описать это интересно, понятно и без нарушения NDA. Вот что получилось! 😊

🧠 Представим кофейню:
Клиент делает заказ. Он участник программы лояльности, у него есть баллы.

☕️ Кассир спрашивает:
"Молодой человек, что делаем с баллами? Списываем или копим?"

👤 Клиент отвечает:
"Списываем!"

Кассир нажимает кнопки и говорит:
"Подтвердите списание!"

Клиент достаёт телефон, заходит в Телеграм и видит сообщение от бота:
💬 "Вы уверены, что хотите списать 324 балла?"
Кнопки: 🔘 Да | 🔘 Нет

Клиент нажимает Да, касса печатает чек.

Как это работает на сервере? 🖥
На сервере процесс списания баллов требует подтверждения. Всё начинается с запроса:

1️⃣ В таблице client_ask фиксируется вопрос клиенту и ожидается его ответ.
2️⃣ На основании ID созданной записи отправляется сообщение клиенту в ТГ.
3️⃣ Сервер ждёт, когда в записи появится ответ в колонке response.

Код выглядит примерно так: 👇

@Service
public class MyService {

@Autowired
private ClientAskRepository clientAskRepository;

@Autowired
private TgMessageService messageService;

@Transactional
public void askClient() {
ClientAsk clientAsk = new ClientAsk(1L, null);
clientAskRepository.save(clientAsk);

// Отправка запроса на подтверждение
messageService.sendClientAsk(clientAsk);

while (clientAsk.getResponse() == null) {
// Ждём ответа
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

clientAsk = clientAskRepository.findById(clientAsk.getId()).orElse(null);
}
}

@Transactional
public void saveClientResponse(Long id, String response) {
clientAskRepository.findById(id).ifPresent(clientAsk -> {
clientAsk.setResponse(response);
clientAskRepository.save(clientAsk);
});
}
}


🤯 Где начинаются проблемы?
Метод askClient создаёт запись и отправляет запрос в ТГ бот. Он ждёт, пока response обновится.

Метод saveClientResponse должен записывать ответ, но...

🚨 Ошибка!
Данные, созданные транзакцией askClient, недоступны для других транзакций, пока они не закоммичены.

Причина: уровень изоляции транзакций по умолчанию — READ_COMMITTED.
Транзакция видит только закоммиченные изменения.

🔧 Как мы решили проблему?
Мы выбрали простой, но спорный метод:
👉 saveClientResponse создаёт новую запись, а askClient ждёт появления новой записи вместо обновления существующей.

Да, это костыль, но он работает. 😅

🛠 Какие ещё есть варианты?
1️⃣ Событийно-ориентированная архитектура:
Можно отправлять события вместо прямого обновления БД.

2️⃣ Выделить создание записи в отдельный сервис:
Это откроет новую транзакцию, которая завершится, и данные станут видимыми.

3️⃣ Изменить уровень изоляции:
Попробовать READ_UNCOMMITTED. Но, увы, в нашем случае это не помогло.

А как бы вы решили такую задачу? Делитесь мыслями в комментариях! ✍️
  • 👍 7
  • ❤ 3
  • ⚡ 2
  • 🔥 1
More from @your_java_mentor
  1. Sep 2, 2026photo post
  2. Sep 2, 2026video post
  3. Sep 1, 2026Post #394
  4. Sep 1, 2026Post #393
  5. Aug 28, 2026Как по вашему, что не так с этим кодом? ❌ В методе main в список добавляются магические ко…
  6. Aug 19, 2026UserService + UserServiceImpl — но Impl тут единственный. Как вы считаете: стоит ли заводи…
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 →