Transactional java что это
The javax.transaction.Transactional annotation provides the application the ability to declaratively control transaction boundaries on CDI managed beans, as well as classes defined as managed beans by the Java EE specification, at both the class and method level where method level annotations override those at the class level.
See the EJB specification for restrictions on the use of @Transactional with EJBs.
This support is provided via an implementation of CDI interceptors that conduct the necessary suspending, resuming, etc. The Transactional interceptor interposes on business method invocations only and not on lifecycle events. Lifecycle methods are invoked in an unspecified transaction context.
If an attempt is made to call any method of the UserTransaction interface from within the scope of a bean or method annotated with @Transactional and a Transactional.TxType other than NOT_SUPPORTED or NEVER, an IllegalStateException must be thrown. The use of the UserTransaction is allowed within life cycle events. The use of the TransactionSynchronizationRegistry is allowed regardless of any @Transactional annotation.
The Transactional interceptors must have a priority of Interceptor.Priority.PLATFORM_BEFORE+200. Refer to the Interceptors specification for more details.
The TxType element of the annotation indicates whether a bean method is to be executed within a transaction context. TxType.REQUIRED is the default.
By default checked exceptions do not result in the transactional interceptor marking the transaction for rollback and instances of RuntimeException and its subclasses do. This default behavior can be modified by specifying exceptions that result in the interceptor marking the transaction for rollback and/or exceptions that do not result in rollback.
The rollbackOn element can be set to indicate exceptions that must cause the interceptor to mark the transaction for rollback.
Conversely, the dontRollbackOn element can be set to indicate exceptions that must not cause the interceptor to mark the transaction for rollback.
When a class is specified for either of these elements, the designated behavior applies to subclasses of that class as well. If both elements are specified, dontRollbackOn takes precedence.
Как Spring @Transactional действительно работает?
В этом посте мы углубимся в управление транзакциями Spring. Мы рассмотрим, как @Transactional действительно работает под капотом. Другие предстоящие сообщения будут включать в себя:
- как использовать такие функции, как распространение и изоляция
- Каковы основные подводные камни и как их избежать
JPA и управление транзакциями
Важно отметить, что JPA сама по себе не обеспечивает какого-либо декларативного управления транзакциями. При использовании JPA вне контейнера внедрения зависимостей, транзакции должны обрабатываться разработчиком программно:
Этот способ управления транзакциями делает область транзакции очень ясной в коде, но имеет несколько недостатков:
- это повторяющееся и подверженное ошибкам
- любая ошибка может иметь очень большое влияние
- ошибки трудно отлаживать и воспроизводить
- это уменьшает читабельность кода
- Что если этот метод вызывает другой транзакционный метод?
Использование Spring @Transactional
В Spring @Transactional приведенный выше код сводится к следующему:
Это намного удобнее и удобочитаемее, и в настоящее время это рекомендуемый способ обработки транзакций в Spring.
Используя @Transactional , многие важные аспекты, такие как распространение транзакций, обрабатываются автоматически. В этом случае, если businessLogic() вызывает другой транзакционный метод, этот метод будет иметь возможность присоединиться к текущей транзакции.
Одним из потенциальных недостатков является то, что этот мощный механизм скрывает то, что происходит под капотом, затрудняя отладку, когда что-то не работает.
Что означает @Transactional ?
Одним из ключевых моментов в @Transactional является то, что нужно рассмотреть две отдельные концепции, каждая со своей областью действия и жизненным циклом:
- постоянный контекст
- транзакция базы данных
Сама аннотация транзакции определяет область действия одной транзакции базы данных. Транзакция базы данных происходит внутри контекста персистентности .
Контекст персистентности находится в JPA EntityManager , реализованном внутренне с использованием Hibernate Session (при использовании Hibernate в качестве поставщика персистентности).
Контекст постоянства – это просто объект синхронизатора, который отслеживает состояние ограниченного набора объектов Java и гарантирует, что изменения в этих объектах в конечном итоге сохраняются обратно в базу данных.
Это совершенно другое понятие, чем понятие транзакции базы данных. Один Entity Manager может использоваться для нескольких транзакций базы данных , и это на самом деле часто.
Когда EntityManager охватывает несколько транзакций базы данных?
Наиболее частый случай, когда приложение использует шаблон Open Session In View для обработки отложенных исключений инициализации, см. Этот предыдущий пост в блоге, чтобы узнать его плюсы и минусы .
В таком случае запросы, которые выполняются на уровне представления, находятся в отдельных транзакциях базы данных, чем те, которые используются для бизнес-логики, но они выполняются через один и тот же менеджер сущностей.
Другой случай, когда контекст постоянства помечен разработчиком как PersistenceContextType.EXTENDED , что означает, что он может выдержать несколько запросов.
Что определяет отношение EntityManager против транзакции?
Это на самом деле выбор разработчика приложения, но наиболее распространенный способ использования JPA Entity Manager – это шаблон «Entity Manager для каждой транзакции приложения». Это наиболее распространенный способ внедрения менеджера сущностей:
Здесь мы по умолчанию находимся в режиме «Entity Manager на транзакцию». В этом режиме, если мы используем этот Entity Manager внутри метода @Transactional , тогда метод будет выполняться в одной транзакции базы данных.
Как работает @PersistenceContext?
Один вопрос, который приходит на ум, заключается в следующем: как @PersistenceContext может внедрить диспетчер сущностей только один раз во время запуска контейнера, учитывая, что менеджеры сущностей настолько недолговечны и что их обычно многократно для каждого запроса.
Ответ заключается в том, что он не может: EntityManager – это интерфейс, и то, что внедряется в bean-компонент Spring, – это не сам менеджер сущностей, а прокси-сервер с учетом контекста , который делегирует конкретному менеджеру сущностей во время выполнения.
Обычно конкретным классом, используемым для прокси-сервера, является SharedEntityManagerInvocationHandler , это можно подтвердить с помощью отладчика.
Как тогда работает @Transactional?
Прокси-контекст постоянства, который реализует EntityManager – не единственный компонент, необходимый для работы декларативного управления транзакциями. На самом деле необходимы три отдельных компонента:
- Сам EntityManager Proxy
- Транзакционный аспект
- Менеджер транзакций
Давайте пройдемся по каждому и посмотрим, как они взаимодействуют.
Транзакционный аспект
Транзакционный аспект – это аспект «вокруг», который вызывается как до, так и после аннотированного бизнес-метода. Конкретный класс для реализации аспекта – TransactionInterceptor .
Транзакционный аспект имеет две основные обязанности:
- В момент «до» аспект обеспечивает точку подключения для определения того, должен ли бизнес-метод, который должен быть вызван, выполняться в области текущей транзакции базы данных или должна быть запущена новая отдельная транзакция.
- В момент «после» аспект должен решить, должна ли транзакция быть зафиксирована, откатана или запущена.
В момент «до» сам Транзакционный аспект не содержит никакой логики принятия решения, решение начать новую транзакцию, если необходимо, делегируется Менеджеру транзакций.
Менеджер транзакций
Менеджер транзакций должен дать ответ на два вопроса:
- должен быть создан новый Entity Manager?
- должна быть запущена новая транзакция базы данных?
Это необходимо решить в тот момент, когда вызывается логика транзакционного аспекта «до». Менеджер транзакций примет решение на основании:
- тот факт, что одна транзакция уже выполняется или нет
- атрибут распространения транзакционного метода (например, REQUIRES_NEW всегда запускает новую транзакцию)
Если менеджер транзакций решит создать новую транзакцию, он будет:
- создать новый менеджер сущностей
- привязать менеджер сущностей к текущему потоку
- захватить соединение из пула соединений с БД
- привязать соединение к текущей теме
Диспетчер сущностей и соединение связаны с текущим потоком с помощью переменных ThreadLocal .
Они хранятся в потоке во время выполнения транзакции, и менеджер транзакций может очистить их, когда они больше не нужны.
Любые части программы, которым нужен текущий менеджер сущностей или соединение, могут извлечь их из потока. Одним из компонентов программы, который делает именно это, является прокси EntityManager.
EntityManager прокси
Прокси-сервер EntityManager (который мы представили ранее) – это последняя часть головоломки. Когда бизнес-метод вызывает, например,
entityManager.persist() , этот вызов не вызывает менеджера сущностей напрямую.
Вместо этого бизнес-метод вызывает прокси-сервер, который извлекает текущий менеджер сущностей из потока, куда его поместил менеджер транзакций.
Теперь, зная, каковы движущиеся части механизма @Transactional , давайте рассмотрим обычную конфигурацию Spring, необходимую для этой работы.
Собираем все вместе
Давайте рассмотрим, как настроить три компонента, необходимые для правильной работы аннотации транзакции. Начнем с определения фабрики менеджера сущностей.
Это позволит внедрить прокси Entity Manager с помощью аннотации постоянного контекста:
Транзакции в Spring Framework
Цель написания данной статьи, как и других, была связана с тем, чтобы попытаться более подробно и детально рассказать, как же все-таки работают транзакции в Spring.
В свое время я столкнулся с трудностями, когда мне пришлось более детально применять на практике свои знания, связанные с транзакциями.
В интернете очень много информации на этот счет, но я постарался изложить максимально компактно и в то же время подробно, что же на самом деле происходит под капотом и как это все устроено.
Надеюсь данная статья будет полезна многим.
Для начала небольшое отступление.
Spring поддерживает два типа управления транзакциями
— Программное управление транзакциями — когда разработчик должен сам управлять транзакциями.
— Декларативное управление транзакциями — означает отделение управления транзакциями от бизнес-логики. Разработчик использует только аннотации в конфигурации на основе XML для управления транзакциями.
Spring создает proxy для классов, объявленных аннотацией @Transactional. Proxy в большинстве случаев невидим во время выполнения. Он предоставляет способ для Spring вводить поведение “до”, “после” или “во время” вызовов методов в proxy-объект.
Поэтому, когда мы определяем метод с @Transactional, Spring динамически создает proxy. Когда клиенты совершают вызовы в наш объект, вызовы перехватываются, а поведение будет вводиться через механизм proxy.
Когда Spring загружает определения бина и настроен на поиск аннотаций @Transactional, он создаст эти proxy-объекты вокруг нашего бина. Эти proxy-объекты являются экземплярами классов, которые будут автоматически генерироваться во время выполнения. Поведение этих proxy-объектов по умолчанию при вызове метода — это просто вызвать тот же метод в “target” бин (т.е. наш бин).
Proxy также могут быть снабжены “перехватчиками”, и, когда они присутствуют, эти “перехватчики” будут вызываться proxy-сервером до того, как он вызовет наш целевой метод бина. Для целевого бина, аннотированного с помощью @Transactional, Spring создаст TransactionInterceptor и передаст его созданному proxy-объекту. Поэтому, когда мы вызываем метод из клиентского кода, мы вызываем метод на proxy-объекте, который сначала вызывает TransactionInterceptor (который начинает транзакцию), который, в свою очередь, вызывает метод в нашем целевом бине. Когда вызов завершается, TransactionInterceptor принимает и завершает/откатывает транзакцию.
Обычно управление транзакциями происходит в service слое приложения.
Начиная с версии 3.1 Spring ввел аннотацию @EnableTransactionManagement, которая используется с аннотацией @Configuration над классами и включает поддержку транзакций.
Понятие транзакционная модель
Транзакционная модель функционирует на уровне транзакций. В рамках одного соединения может быть много транзакций. От одного пользователя может быть много одновременных и независимых соединений а также конкурентных транзакций тоже может быть много.
Если взять за основу такие известные реляционные базы как MySQL и PostgreSQL — одно соединение одновременно держать две разные транзакции открытыми не может, т.к. одно соединение — только одна открытая транзакция. Нет возможности в рамках одного соединения выполнять одновременно несколько команд. Они предоставляют неблокирующий вызов, но не дождавшись конца ответа новые запросы отправлять у вас не получится.
Например в MySQL каждый “thread” держит соединение. Он не отслеживает, каким процессом это соединение открыто, т.е. ему без разницы, один процесс открыл множество соединений или это множество процессов. MySQL выполняет запросы параллельно, в рамках “тредной модели”, но существуют блокировки.
Какие бывают транзакционные модели ?
1. Local — голый JDBC
2. Programmatic — JPA
3. Declarative — Spring (когда мы ставим аннотацию)
Интересно, что же происходит внутри Spring при объявлении аннотации @Transactional ?
В Spring есть такое понятие как SpringDefaultNameConvension — именование бинов по умолчанию.
По умолчанию Spring ищет есть ли у нас бин с именем TransactionManager. Если есть, то Spring его найдет и сам подключит, нам не надо его дополнительно настраивать.
Но если у нас несколько TransactionManager, тогда мы должны указать явно. Дальше указываем дополнительные параметры, такие как уровни изоляции транзакций, свойство propagation и т.д.
1. Поставили аннотацию и объявили TransactionManager
TransactionManager создает EntityManager, если он необходим, и осуществляет старт новой транзакции. В зависимости от того, выполняется ли хоть одна транзакция в текущий момент или нет и параметра “propagation” у метода, аннотированного @Transactional, создается новая транзакция.
Алгоритм создания новой транзакции:
— создается новый EntityManager
— EntityManager привязывается к “текущему потоку Thread”
— берется соединение из пула соединений БД
— это соединение привязывается к “текущему потоку Thread” при помощи ThreadLocal (Класс ThreadLocal предоставляет локальные переменные потока. Каждый поток имеет свою собственную инициализированную копию переменной)
@Configuration
@EnableTransactionManagement
public class Config <@Autowired
EntityManagerFactory factory;@Autowired
private DataSource dataSource;
@Bean(name = “transactionManager”)
public PlatformTransactionManager transactionManager() <
JpaTransactionManager tm =
new JpaTransactionManager();
tm.setEntityManagerFactory(emf);
tm.setDataSource(dataSource);
return tm;
>
>
Аннотация @EnableTransactionManagement означает, что классы, помеченные @Transactional, должны быть обернуты аспектом транзакций.
2. Прописываем EntityManagerFactory если используем JPA, прописываем DataSource если JDBC и т.д.
<bean > <property name=”entityManagerFactory” ref=”entityManagerFactory”>
</bean>
@Bean(name = “entityManagerFactory”)
public LocalContainerEntityManagerFactoryBean factory() <
LocalContainerEntityManagerFactoryBean factory = …
factory.setDataSource(dataSource);
factory.setPackagesToScan(
new String[] <“your.package”>);
factory.setJpaVendorAdapter(
new HibernateJpaVendorAdapter());
return factory;
>
>
3. Поставить тег <tx:annotation-driven proxy-target-class=”true”/> — именно он запускает весь механизм работы Spring JTA !
Что он делает в Spring config ?
Мы знаем что Spring работает по принципу проксирования:
1. Для бинов он создает proxy
2. Используется подход AOP для добавления какого-то поведения.
3.1 Spring начинает работать, выполнять свои классы и первый из них это AopAutoProxyConfigurer, который вызывает метод configureAutoProxyCreator().
3.2 Потом создается класс TransactionInterceptor.
3.3 Далее Spring находит наш TransactionManager и подключает его к TransactionInterceptor.
3.4 Spring регистрирует TransactionInterceptor как бин.
Аннотация @Transactional определяет область действия одной транзакции БД. Транзакция БД происходит внутри области действий persistence context. Persistence context в JPA является EntityManager, который использует внутри класс Session ORM-фреймворка Hibernate (если использовать Hibernate как persistence провайдер).
Один объект EntityManager может быть использован несколькими транзакциями БД.
Есть такое понятие как EntityManager proxy.
Например, когда происходит вызов метода entityManager.persist(), он не вызывается напрямую у EntityManager. Вместо этого вызывается прокси, который достает текущий EntityManager из потока, в который его положил менеджер транзакций.
Подход через аннотации намного удобен и удобочитаем и поэтому рекомендуется использовать именно его.
Как @Transactional парсится и что Spring с ней делает ?
У Spring есть специальный парсер для этого — SpringTransactionAnnotationParser (используется по умолчанию).
Что делает TransactionInterceptor ?
1. Считывает атрибуты (параметры), которые были у аннотации @Transactional
txAttr = getTRansactionAttributeSource().
getTransactionAttribute(invocation.getMethod(),
targetClass);
2. Предоставляет интерфейс PlatformTransactionManager — основной интерфейс для всех TransactionManager (TransactionManager взаимодействует непосредственно с БД для управлением транзакций)
PlatformTransactionManager ptm = determineTransactionManager(txAttr);
Spring сам по себе транзакциями не управляет. Он является прослойкой между вашим декларативным описанием и конечной базой данных. Он все делегирует базе данных. Spring — это просто удобный для нас способ объявить или “менеджить” транзакции, но внутри себя Spring не содержит чего-то такого, что можно было бы назвать транзакцией.
3. Идет создание транзакций
TransactionInfo trInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
В зависимости от того, как мы сконфигурировали атрибуты, транзакцию можно начать или нет.
4. Происходит вызов основного метода
retVal = invocation.proceed();
> catch(Throwable ex) <
completeTransactionAfterThrowinf(trInfo, ex);
>
где invocation.proceed()— вызов основного транзакционного метода с кодом
5. Происходит commit транзакции если нет exception
Что касается настроек, так в XML мы можем указать такие параметры, как:
все методы, начинающиеся на find, сделать readOnly, а методы, начинающиеся на create получают откат транзакции в случае возникновения например SQLException и не откатываются в случае получения RuntimeException, уровни изоляции и другие.
<tx:advice transactionManager=”myManager”>
<tx:attributes>
<tx:method name=”find*” read-only=”true”/>
<tx:method name=”create*” rollback-for=”SQLException” no-rollback-for=”RuntimeException” isolation=”SERIALIZABLE” propagation=”MANDATORY”/>
</tx:attributes>
</tx:advice>Прописываем DataSource, url, login, password и класс драйвера для вашей БД.
<bean > <property name=”dataSource” ref=”dataSource”/>
</bean>
Также можно указать для какого конкретно класса можем использовать эти правила, что позволяет более гибко настраивать логику. Указываем, что данное правило будет применяться для всех методов в классе FindInformationService будет использоваться конфигурация transactionRule01.
<aop:pointcut expression=”execution(* com.helpdesk.FindInformationService.*(…))”/>
<aop:advisor advice-ref=”transactionRule01" pointcut-ref=”findInformationServiceByUser”>
В Spring есть абстрактный класс AbstractPlatformTransactionManager, который является основным для всех TransactionManager. Он определяет транзакции, применяет уже сконфигурированный уровень propagation, может приостанавливать и возобновлять транзакции, установка флага rollbackOnly для определенного действия (см. выше) и т.д.
Рассмотрим в качестве примера JPATransactionManager.
1. получили EntityManagerFactory
2. получили DataSource
3. исходя из диалекта (какая у нас БД) создается новая транзакция beginTransaction()
т.е. Spring ничего не делает, он просто “говорит” что знает какая у нас БД и предоставляет нам соответствующий для неё диалект и т.д.
4. зарегистрировали полученное соединение в ThreadLocal объекте.
Все соединения и транзакции являются потокозависимыми (все выполняется в текущем потоке), т.е. когда мы начали транзакцию в своем потоке мы не можем ее как-то напрямую расширить на другой поток (в этом случае будет начинаться новая транзакция). Разве что можно отключить транзакцию от потока и “приаттачить” к другому.
5. beginTransaction()
prepareTransaction()
commitTransaction()
Немаловажными компонентами являются интерфейсы TransactionDefinition и TransactionStatus.
TransactionDefinition содержит в себе конфигурацию транзакции, уровень изоляции и propagation.
TransactionStatus возвращает статус текущей транзакции.
Это все конечно хорошо, но как же транзакции работают в приложениях ?
Существует стандарт XA (eXtended Architecture), который связывает наш источник данных (dataSource) с TransactionManager. Базы данных должны реализовывать этот стандарт внутри себя, чтобы TransactionManager мог управлять ими и создавать транзакции.
Этот стандарт поддерживает 2 типа коммитов:
1. однофазный — работает с одним источником данных (1 база данных) — только 1 транзакция — либо выполнилась либо нет.
2. двухфазный — работает с 2 или более источником данных — имеется несколько DataSource, внутри каждого есть своя транзакция и есть 1 глобальная транзакция для этих внутренних транзакций
Транзакции работают в своем потоке.
Есть такой термин как transaction context — он обязательно имеет id, который обозначает, что транзакция работает в таком-то контексте. Также может иметь другие параметры.
Какие бывают TransactionManager ?
1. DataSourceTransactionManager — для JDBC
2. HibernateTransactionManager — для Hibernate
3. JPATransactionManager — для JPA (обычно подходит для приложений, которые используют один JPAEntityManagerFactory для доступа к транзакционным данным)
4. JTATransactionManager — для JTA (обычно необходим для доступа к нескольким транзакционным ресурсам в рамках одной транзакции)
5. JDOTransactionManager
6. JmsTransactionManager
7. WebLogicJtaTransactionManager
8. WebSphereUowTransactionManager — реализация PlatformTransactionManager для WebSphere
9. CallbackPreferringPlatformTransactionManager — предоставляет методы для выполнения обратного вызова в транзакции
10. PlatformTransactionManager — центральный интерфейс в транзакционной инфраструктуре Spring
11. AbstractPlatformTransactionManager — реализует определенное поведение propagation и заботится об обработке синхронизации транзакций
12. ResourceTransactionManager — в основном используется для абстрактного самоанализа менеджера транзакций, давая клиентам подсказку о том, какой менеджер транзакций им был предоставлен и над каким конкретным ресурсом работает менеджер транзакций
13. CciLocalTransactionManager
14. OC4JJtaTransactionManager — вариант JtaTransactionManager для Oracle OC4J (10.1.3 и выше)
…
По типу TransactionManager делятся на:
1. Локальные транзакции — работают только с одним ресурсом (используют однофазный коммит)
2. Глобальные транзакции — работают с 2 или более ресурсами (используют двухфазный коммит). Используют XA стандарт.
а. JtaTransactionManager
b. OC4JJtaTransactionManager
c. WebLogicJtaTransactionManager
d. WebSphereUowTransactionManager
Они являются двухфазными, т.е. с их помощью можно одновременно управлять транзакциями и в БД и например в JMS. Т.е мы хотим чтобы данные записались в БД и были отправлены в JMS как “одно целое”.
3. Distributed транзакции:
Например мы имеем сложное по структуре приложение, расположенное в разных местах: одна Java машина в одном месте, другая Java машина в другом месте, в третьем третья и т.д.). Это позволяет для всех них иметь одну транзакцию. На данный момент Spring такого сделать не cможет.
С помощью аннотаций можно указать в коде какой менеджер транзакций вы хотите использовать.
Какие есть типы транзакций ?
1) Physical — единая транзакция, которая действует сразу для нескольких методов.
2) Logical — транзакция каждого метода. Логичекие транзакции могут быть внутри одной Physical транзакции. Эти внутренние тарнзакции (Logical) могут влиять на внешнюю (Physical): например, если одна из внутренних транзакций выбросила exception, то вся внешняя транзакция м.б. отменена и т.д.
Про то, что транзакции должны соответствовать принципу ACID и уровни изоляции транзакций я говорить не буду. На просторах интернета немало сказано на этот счет.
Цель данной статьи была поделиться своими знаниями в области транзакций Spring, которых мне нехватало в свое время и пришлось столкнуться с трудностями в понимании работы.
Оставлю здесь как шпаргалку свойств propagation для транзакций
1. MANDATORY — использует существующую транзакцию. Если ее нет — бросает exception. Если используется для класса, то действует на все public методы.
2. NESTED — вложенная транзакция (подтранзакция). Подтвержается вместе с внешней транзакцией. Если нет существующей транзакции — работает как REQUIRED.
Можно применять в таких случаях, когда: Сработает — норм, не сработает — тоже норм. Самое главное чтобы внешняя транзакция не пострадала.
3. NEVER — означает, что данный метод не должен выполняться в транзакции. Если транзакция запущена — бросает exception.
4. NOT_SUPPORTED — означает не выполнять в текущей транзакции. Если транзакция запущена — она останавливается на время выполнения метода. Метод выполняется вне транзакции. Когда метод выполнился — транзакция запускается.
5. REQUIRED — (по умолчанию) означает, что если запущена транзакция — выполнять внутри нее, иначе создает новую транзакцию. Если ошибка в запросе, то в базу ничего на запишется.
6. REQUIRES_NEW — создает в любом случае новую транзакцию. Если запущена существующая транзакция — она останавливается на время выполнения метода, новый метод выполняется в новой транзакции, и дальше выполняется внешняя транзакция, если она есть.
7. SUPPORTS — может выполняться внутри транзакции, если она запущена, иначе выполнять без транзакции (новую транзакцию не создает), т.е. методу не важно, будет транзакция или нет, он в любом случае выполнится, но если будет транзакция, то он выполнится внутри нее.
Параметры аннотации @Transactional:
@Transactional(value = “testTransaction”, isolation = Isolation.SERIALIZABLE, propagation = Propagation.SUPPORTS, readOnly = true, timeout = 1, rollbackFor = “IOException”)
где, readOnly — имеет значение только внутри транзакции. Если операция происходит вне контекста транзакции, флаг просто игнорируется
timeout — выставляется именно для транзакций (надо вставлять в том месте, где начинается новая транзакция). По умолчанию, если возникает
RuntimeException — то транзакция будет откатываться. Если checked exception — то транзакция не будет откатываться. По умолчанию используется таймаут, установленный по умолчанию для базовой транзакционной системы.
rollbackFor — указываем роллбэк для определенного exception
rollbackForClassName
noRollbackFor — Указывает, что откат не должен происходить, если целевой метод вызывает исключение, которое вы укажете.
noRollbackForClassName
Интересный момент:
Spring автоматически откатывает транзакции для unchecked (Runtime) исключений:
Например если один транзакционный метод вызывает другой транзакционный метод в другом классе и этот внутренний вызов выбрасывает Runtime exception, то вся транзакция целиком будет отменена. В этом случае можно использовать параметр noRollBackFor или выполнить внутреннюю транзакцию в новой транзакции (свойства propagation).
Аннотация @Transactional будет проигнорирована и не выбросит исключения если применять ее к private, protected или default модификаторами доступа.
С помощью TransactionSynchronizationManager.isActualTransactionActive() можно отслеживать какую-либо внешнюю транзакцию, если она у вас есть.
Надеюсь данная статья будет полезна.
Если вы нашли неточности в описании данной статьи, вы можете написать мне на email и я с радостью вам отвечу.
Annotation Interface Transactional
When this annotation is declared at the class level, it applies as a default to all methods of the declaring class and its subclasses. Note that it does not apply to ancestor classes up the class hierarchy; inherited methods need to be locally redeclared in order to participate in a subclass-level annotation. For details on method visibility constraints, consult the Transaction Management section of the reference manual.
This annotation is generally directly comparable to Spring’s RuleBasedTransactionAttribute class, and in fact AnnotationTransactionAttributeSource will directly convert this annotation’s attributes to properties in RuleBasedTransactionAttribute , so that Spring’s transaction support code does not have to know about annotations.
Attribute Semantics
If no custom rollback rules are configured in this annotation, the transaction will roll back on RuntimeException and Error but not on checked exceptions.
Rollback rules determine if a transaction should be rolled back when a given exception is thrown, and the rules are based on types or patterns. Custom rules may be configured via rollbackFor() / noRollbackFor() and rollbackForClassName() / noRollbackForClassName() , which allow rules to be specified as types or patterns, respectively.
When a rollback rule is defined with an exception type, that type will be used to match against the type of a thrown exception and its super types, providing type safety and avoiding any unintentional matches that may occur when using a pattern. For example, a value of jakarta.servlet.ServletException.class will only match thrown exceptions of type jakarta.servlet.ServletException and its subclasses.
When a rollback rule is defined with an exception pattern, the pattern can be a fully qualified class name or a substring of a fully qualified class name for an exception type (which must be a subclass of Throwable ), with no wildcard support at present. For example, a value of «jakarta.servlet.ServletException» or «ServletException» will match jakarta.servlet.ServletException and its subclasses.
WARNING: You must carefully consider how specific a pattern is and whether to include package information (which isn’t mandatory). For example, «Exception» will match nearly anything and will probably hide other rules. «java.lang.Exception» would be correct if «Exception» were meant to define a rule for all checked exceptions. With more unique exception names such as «BaseBusinessException» there is likely no need to use the fully qualified class name for the exception pattern. Furthermore, rollback rules defined via patterns may result in unintentional matches for similarly named exceptions and nested classes. This is due to the fact that a thrown exception is considered to be a match for a given pattern-based rollback rule if the name of thrown exception contains the exception pattern configured for the rollback rule. For example, given a rule configured to match against «com.example.CustomException» , that rule will match against an exception named com.example.CustomExceptionV2 (an exception in the same package as CustomException but with an additional suffix) or an exception named com.example.CustomException$AnotherException (an exception declared as a nested class in CustomException ).
For specific information about the semantics of other attributes in this annotation, consult the TransactionDefinition and TransactionAttribute javadocs.
Transaction Management
This annotation commonly works with thread-bound transactions managed by a PlatformTransactionManager , exposing a transaction to all data access operations within the current execution thread. Note: This does NOT propagate to newly started threads within the method.
Alternatively, this annotation may demarcate a reactive transaction managed by a ReactiveTransactionManager which uses the Reactor context instead of thread-local variables. As a consequence, all participating data access operations need to execute within the same Reactor context in the same reactive pipeline.