В КУРСЕ?

Разбираемся в теме

Spring 5 и JPA: почему @Transactional не гарантирует откат после любой ошибки?

Представим сервис на Spring 5 с JPA: он меняет описание товара и добавляет запись о выполненной операции. Разработчик поставил @Transactional и ожидает, что любая ошибка отменит оба изменения. Но результат зависит от способа вызова и настроек отката. Разберём обычный режим работы через прокси, без переноса особенностей более новых версий на старую конфигурацию.

Транзакция относится к целой операции

Если изменение товара и запись о действии должны сохраняться вместе, сначала сформулируйте это как требование. Успешный вызов оставляет оба результата, неуспешный в предусмотренном сценарии не оставляет частично выполненную работу. Поэтому границу часто располагают на сервисном методе, объединяющем действия. Наличие отдельных вызовов сохранения ещё не доказывает, что весь составной процесс охвачен одной транзакцией. Полезно нарисовать последовательность и указать, в какой момент она должна либо завершиться целиком, либо откатиться.

Аннотация должна быть обработана инфраструктурой

В стандартном режиме Spring 5 перехватывает внешние вызовы, проходящие через транзакционный прокси. Для такого варианта рассматривают публичный метод управляемого Spring объекта при включённой транзакционной поддержке. Если один метод объекта напрямую вызывает другой метод того же объекта, аннотация внутреннего метода сама по себе не создаёт новую транзакционную границу через прокси. Если внешняя транзакция уже существовала, внутренний код может выполняться в ней. Поэтому отсутствие нового перехвата не следует путать с обязательным отсутствием любой транзакции.

Тип исключения влияет на стандартный откат

Без специальных правил Spring по умолчанию откатывает транзакцию при RuntimeException и Error, вышедших к транзакционному перехватчику. Проверяемое исключение само по себе не вызывает такой откат по умолчанию. Нужное поведение можно задать через rollbackFor и связанные настройки. Если код перехватил ошибку и вернулся как после успеха, инфраструктура не обязана догадаться о намерении разработчика. Следует отдельно проверить, какое исключение пересекает границу и не была ли транзакция уже отмечена для отката другим механизмом.

Проверка должна наблюдать данные после завершения

Для учебного сервиса подготовьте исходное описание товара и заранее известное отсутствие новой записи в журнале. Затем рассмотрите успешный путь и ошибку между двумя действиями. В настоящей проверке результат оценивают после завершения транзакционного вызова, читая итоговое состояние подходящим независимым способом. Одно сообщение в логе или значение объекта в памяти не доказывает, что изменения зафиксированы в базе. Важно также проверить реальный маршрут вызова: тест, обходящий прокси, может исследовать совсем другое поведение, чем приложение.

Попробуйте на практике

Разберите три схемы вызова на бумаге. Это логическая проверка для Spring 5, а не выполненный запуск приложения.

  1. Нарисуйте вызывающий компонент, транзакционный прокси и публичный сервисный метод, который должен объединить изменение товара и запись в журнале.
  2. Для первого случая задайте внешнее обращение через прокси и необработанное RuntimeException между действиями. Укажите ожидаемый откат при стандартных настройках.
  3. Во втором случае замените исключение на проверяемое и сохраните остальные предпосылки. Отметьте, почему стандартного правила отката недостаточно для прежнего ожидания.
  4. В третьем случае внешний нетранзакционный метод напрямую вызывает аннотированный метод того же объекта. Укажите, где обходится прокси, не делая предположений о самостоятельных транзакциях нижележащих компонентов.
  5. Для каждого случая запишите, какие итоговые данные и настройки нужно проверить в реальном тесте. Отдельно обозначьте возможное существование внешней транзакции.

Как проверить результат. В схеме различаются бизнес-требование, перехват вызова и правило отката. Аннотация не считается доказательством поведения без проверки маршрута, исключения и итогового состояния данных.

Частые вопросы

Внутренний вызов всегда выполняется без транзакции?

Нет. Он может находиться внутри уже начатой транзакции. Ограничение касается применения аннотации через прокси при прямом вызове метода того же объекта.

Достаточно ли просто поймать исключение и вернуть ошибку?

Это не равнозначно автоматическому откату. Нужное транзакционное поведение должно быть явно обеспечено и проверено с учётом настроек и состояния транзакции.

Самостоятельный разбор темы. Содержание конкретной обучающей программы здесь не представлено.

Зарегистрируйтесь, чтобы уточнить возможность доступа к этому материалу

Зарегистрироваться
← К списку материалов