В этом посте я соберу самые лучшие практики использования Spring Transactional
Service или DAO?
- Устанавливайте аннотацию
@Transactional
в слое сервиса (Service layer), а не в слое DAO. Сервисные бины могут использовать несколько DAO, соблюдая ACID под одной и той же транзакцией. Иначе если только в DAO определен механизм транзакций, то сервисные бины увеличат расходы на создание множественных транзакций для концептуально сгруппированных операций, не говоря уже о неконсистентном состоянии данных, которое мы рискуем получить.
- В этом пункте, продолжаем мысль из предыдущего, мы можем задать
@Transactional(propagation = Propagation.MANDATORY)
на уровне класса в наших DAO, таким образом заставляя потребителей наших DAO инициировать управление транзакциями. - Необходимо знать дефолтное поведение аннотации
@Transactional
и не использовать ее вслепую. А именно, если не указано явно, уровень распространения (propagation) установится вPropagation.REQUIRED,
что означает использование текущей транзакции, иначе создать новую; изоляция установится в значениеIsolation.DEFAULT,
это значение определяется нижележащей БД (по умолчанию у многих БД это значение равноIsolation.READ_COMMITED)
;readOnly
флаг выключен по умолчанию;rollbackFor
может быть задано сThrowable
классом, но будьте осторожны: по умолчанию откат (rollback) происходит только когда выбрасываетсяRuntimeException
(если не установлен этот параметр). - Будьте осторожны с флагом
readOnly
. Хотя@Transactional(readOnly = true, propagation=Propagation.REQUIRED)
выбросит исключение при попытке выполниться команде вставки/обновления через JDBC в транзакции, она может иметь неожиданное поведение при попытке вставки/обновления через ORM, где операция скрытно выполнится и успешно выполнится коммит транзакции. В ORM окружении необходимо использовать этот флаг вместе сPropagation.SUPPORTS
. В этом случае мы не будем платить стоимость создания новой транзакции в простой операции выборки. Или даже рассмотрите возможность избавиться от управления транзакциями для операций SELECT. - Будьте осторожны при использовании
Propagation.REQUIRES_NEW
не на высшем уровне транзакций. Это может вызвать многочисленные проблемы. Каждый раз как новая нижележащая транзакция оборачивается этим аспектом, в случаях когда множественные транзакции сREQUIRES_NEW
s включены в пределах одного транзакционного сервисного метода могут возникнуть проблемы с потерей данных. С другой стороны, не возбраняется использовать аннтацию на транзакционном методе высшего уровня, что на самом деле аналогично поведению дефолтногоPropagation.REQUIRED
Spring автоматически откатывает транзакции для unchecked (Runtime) исключений
Когда возникает Runtime exception, то Spring помечает текущую транзакцию для отката.
Возможно самой запутанной частью таких откатов явлется наблюдение такого сообщения «UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only».
Это ожидаемое поведение, но может быть запутанным, если не понимать что произошло на самом деле. В случаем если один транзакционный метод вызывает другой транзакционный метод в другом классе и этот внутренний вызов выбрасывает Runtime exception, то вся транзакция целиком будет отменена. Это будет происходить, если Вы не ловите Exception в вызывающем классе. Если откат внешнего класса не желателен, Вы можете либо выполнить внутреннюю транзакцию в новой транзакции (используя другие propagation) или использовать атрибут noRollBackFor
аннотации @Transactional
.
Не используйте аннотацию @Transactional на приватных, protected или default методах.
Этот пункт происходит из первого пункта о том как Spring управляет транзакциями. Добавление аннотации к методами с private, protected или default модификаторами доступа не выбросит исключение.Однако аннотация будет проигнорирована
Логирование транзакций
Очень важно в случае возникновения непредвиденных ситуаций смотреть и понимать логи. Для включения логов необходимо добавить в log4j.properties
org.springframework.transaction=TRACE
Это позволит Вам отследить активности происходящие внутри транзакционных процессов. Логи обеспечивают детальную информацию о новых, переиспользованных и активных транзакциях, режимах транзакций, коммитов транзакций. Это наилучший способ понять как транзакции управляют Вашим приложением и решить проблему.
Небольшая справка по атрибуту Transactional Propagation
@Transactional(propagation=Propagation.REQUIRED)
Если не указано, то стратегия распространения по дефолту это REQUIRED.
Другие опции это REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER, и NESTED.
REQUIRED
- Означает что целевой метод не может быть запущен без активной транзакции. Если транзакция уже стартовала до вызова этого метода, тогда методы выполнится в ней или иначе при вызове метода новая транзакция будет создана.
REQUIRES_NEW
- Означает что новая транзакция должна начинаться всякий раз, как целевой метод будет вызываться. Если уже есть начатая транзакция, то она будет приостановлена, до начала новой.
MANDATORY
- Означает что целевой метод требует активной транзакции для старта. Если ее нет, то выполнение не будет произведено, и выбросится исключение.
SUPPORTS
- Означает что целевой метод может быть исполнен вне транзакции. Если есть начатая транзакция, то метод запустится в ней. Если нет запущенной транзакции, то метод выполнится все равно, только не в транзакционом контексте.
- Методы, которые выполняют выборку данных наилучшие кандидаты для этой опции.
NOT_SUPPORTED
- Означает что целевой метод не требует транзакционного контекста для выполнения. Если есть начатая транзакция, то она будет приостановлена.
NEVER
- Означает что целевой метод выбросит исключение, если выоплнится в транзакционном процессе.
- Не советую использовать эту опцию.
@Transactional (rollbackFor=Exception.class)
- По умолчанию откат происходит при rollbackFor=RunTimeException.class
- В спринге, все API классы выбрасывают RuntimeException, что означает что если любой метод упал, то контейнер всегда произведет откат текущей транзакции.
- Проблема только с checked exceptions. Так что данная опция может использоваться для декларативного отката транзакции если выбросится Checked Exception.
@Transactional (noRollbackFor=IllegalStateException.class)
- Означает что откат не должен происходить если целевой метод выбросил это исключение.