Декларативне управління транзакціями в Spring Framework стало можливим завдяки аспектно-ориєнтованому програмуванню (АОП) в Spring. Однак, оскільки код транзакційних аспектів поширюється разом з дистрибутивом Spring Framework і може застосовуватися як шаблон, для ефективного використання цього коду зазвичай не потрібно розуміння концепцій АОП.
Декларативне управління транзакціями в Spring Framework схоже на CMT из EJB, оскільки можна
визначити логіку роботи транзакцій (чи її відсутність) до рівня окремих методів. У разі потреби можна здійснити
виклик
setRollbackOnly()
у контексті транзакції. Відмінності між двома типами управління транзакціями
полягають ось у чому:
На відміну від CMT з EJB, який прив'язаний до JTA, декларативне управління транзакціями у Spring Framework працює у будь-якому оточенні. Він може працювати з транзакціями JTA або локальними транзакціями за допомогою JDBC, JPA або Hibernate шляхом коригування конфігураційних файлів.
Можна застосовувати декларативне управління транзакціями в Spring Framework до будь-якого класу, а не лише до спеціальних класів, таких як класи EJB.
Spring Framework пропонує декларативні правила відкату, які не мають аналогів у EJB. Передбачено як програмну, так і декларативну підтримку правил відкату.
Spring Framework дозволяє налаштовувати логіку роботи транзакцій за допомогою АОП. Наприклад, можна впровадити кастомну логіку роботи в разі відкату транзакції. Також можна додавати довільні Advice поряд із транзакційними Advice. У разі використання CMT з EJB не можна впливати на управління транзакціями контейнера, крім як за допомогою
setRollbackOnly()
.Spring Framework не підтримує поширення контекстів транзакцій через віддалені дзвінки, як це роблять сервери додатків вищого класу. Якщо потрібна ця функція, ми рекомендуємо використовувати EJB. Однак добре подумай, перш ніж використовувати таку функцію, тому що зазвичай транзакції не повинні охоплюватися віддаленими викликами.
Концепція правил відкату дуже важлива. Ці правила дозволяють вказати, які винятки (і події, що
кидаються) повинні призводити до автоматичного відкату. Ти можеш поставити це декларативно, у конфігурації, а
не в коді Java. Тому, хоча ви все ще можете викликати setRollbackOnly()
для об'єкта TransactionStatus
,
щоб відкотити поточну транзакцію, краще встановити правило, згідно з яким виняток MyApplicationExceptionException
завжди має призводити до відкату. Істотною перевагою цього варіанта є те, що бізнес-об'єкти не
залежатимуть від транзакційної інфраструктури. Наприклад, вони зазвичай не вимагають імпортування
API-інтерфейсів транзакцій Spring чи інших API-інтерфейсів Spring.
Хоча логіка роботи контейнера EJB за замовчуванням автоматично відкочує транзакцію в разі системного винятку
(зазвичай винятку часу виконання), CMT з EJB не відкочує транзакцію автоматично при
винятку програми (тобто винятку, що перевіряється, відмінному від java.rmi.RemoteException
).
Хоча логіка роботи Spring за замовчуванням для декларативного управління транзакціями слідує угоді EJB (відкат
відбувається автоматично лише за неперевірених винятків), часто буває корисно налаштувати цю логіку роботи.
Загальні відомості про реалізації декларативної транзакції в Spring Framework
Мало просто сказати, що потрібно позначити класи анотацією @Transactional
, додати анотацію
@EnableTransactionManagement
до твоєї конфігурацію і чекати, що ти зрозумієш, як все це працює.
Щоб дати більш глибоке розуміння, в цьому розділі описані внутрішні механізми декларативної транзакційної
інфраструктури Spring Framework у контексті питань, пов'язаних з транзакціями.
Найбільш важливі концепції, які необхідно опанувати щодо підтримки декларативних транзакцій у Spring Framework,
полягають в тому, що ця підтримка
забезпечується завдяки проксі АОП і
що транзакційні Advice керуються метаданими (в даний час на основі XML або анотацій).
Поєднання АОП з транзакційними метаданими дає проксі АОП, який задіює TransactionInterceptor
у
поєднанні з відповідною реалізацією TransactionManager
для керування транзакціями навколо
викликів методів.
TransactionInterceptor
у Spring Framework забезпечує управління транзакціями для імперативних та
реактивних моделей програмування. Перехоплювач визначає бажаний тип управління транзакціями, перевіряючи тип
методу, що повертається. Методи, що повертають реактивний тип, такий як Publisher
або Flow
(або їх підтип) мовою Kotlin, підходять для управління реактивними транзакціями. Всі інші типи, що
повертаються, включно з void
, використовують шлях виконання коду для імперативного управління
транзакціями.
Особливості управління транзакціями впливають на те, який диспетчер транзакцій потрібен. Імперативні
транзакції вимагають PlatformTransactionManager
, а реактивні транзакції використовують
реалізацію ReactiveTransactionManager
.
Анотація @Transactional
зазвичай працює з
прив'язаними до потоку транзакціями, що управляються PlatformTransactionManager
, відкриваючи
транзакцію для всіх операцій доступу до даних у чинному потоці, що виконується. Примітка: це не
поширюється на знову запущені потоки всередині методу.
Реактивна транзакція, якою управляє ReactiveTransactionManager
, використовує контекст із Reactor
замість локальних атрибутів потоку. Як наслідок, всі операції доступу до даних, що беруть участь,
повинні виконуватися в одному і тому ж контексті з Reactor в тому самому реактивному конвеєрі.
На наступному малюнку показано концептуальне подання виклику методу для транзакційного проксі:
Приклад реалізації декларативної транзакції
Розглянемо наступний інтерфейс та його відповідну реалізацію. У цьому прикладі класи Foo
та
Bar
використовуються як плейсхолдери, щоб ви могли зосередитися на використанні транзакцій, не
відволікаючись на конкретну модель предметної області. Для цілей цього прикладу той факт, що клас DefaultFooService
генерує екземпляри UnsupportedOperationException
у тілі кожного реалізованого методу, є
позитивним. Така логіка роботи дозволяє зрозуміти, як утворюються транзакції, а потім відкочуються у
відповідь на екземпляр UnsupportedOperationException
. У наступному лістингу показаний інтерфейс
FooService
:
// інтерфейс служби, який ми хочемо зробити транзакційним
package x.y.service;
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
// інтерфейс служби, який ми хочемо зробити транзакційним
package x.y.service
interface FooService {
fun getFoo(fooName: String): Foo
fun getFoo(fooName: String, barName: String): Foo
fun insertFoo(foo: Foo)
fun updateFoo(foo: Foo)
}
У наступному прикладі показано реалізацію попереднього інтерфейсу:
package x.y.service;
public class DefaultFooService implements FooService {
@Override
public Foo getFoo(String fooName) {
// ...
}
@Override
public Foo getFoo(String fooName, String barName) {
// ...
}
@Override
public void insertFoo(Foo foo) {
// ...
}
@Override
public void updateFoo(Foo foo) {
// ...
}
}
package x.y.service
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Foo {
// ...
}
override fun getFoo(fooName: String, barName: String): Foo {
// ...
}
override fun insertFoo(foo: Foo) {
// ...
}
override fun updateFoo(foo: Foo) {
// ...
}
}
Припустимо, що перші два методи інтерфейсу FooService
, getFoo(String)
та getFoo(String,
String)
, повинні виконуватися в контексті певної транзакції з семантикою "тільки для читання", а
інші методи insertFoo(Foo)
та updateFoo(Foo)
повинні виконуватися в контексті
транзакції з семантикою "читання-запис". Наступна конфігурація описана в наступних кількох абзацах:
<!-- з файлу "context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns: tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- це об'єкт-служба, який ми хочемо зробити транзакційним -->
<bean id="fooService" class= "x.y.service.DefaultFooService"/>
<!-- транзакційний Advice (що "відбувається"; див. <aop:advisor/> bean нижче) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- ранзакційна семантика...... -->
<tx:attributes>
<!-- всі методи, що починаються з "get", доступні тільки для читання -->
<tx:method name="get*" read-only="true"/>
<!-- інші методи використовують параметри транзакції за умовчанням (див. нижче) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- переконуємося, що вищезгаданий транзакційний Advice виконується
за будь-якого виконання операції, визначеної інтерфейсом FooService -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- не забуваємо про DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- Аналогічно, не забуваємо про TransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- інші визначенн <bean/--> -->
</beans>
Вивчи попередню конфігурацію. Передбачається, що вам потрібно зробити об'єкт-службу, бін fooService
,
транзакційним. Семантика транзакції, яку необхідно застосувати, полягає у визначенні <tx:advice/>
.
Визначення <tx:advice/>
свідчить: "всі методи, що починаються з get
,
повинні виконуватися в контексті транзакції "тільки для читання", а всі інші методи повинні виконуватися
із семантикою транзакції за замовчуванням". Атрибут transaction-manager
тега
<tx:advice/>
вказується в імені біна TransactionManager
, який керуватиме транзакціями (в даному випадку
це бін txManager
).
transaction-manager
у
транзакційному Advice ( <tx:advice/>
), якщо ім'я біна TransactionManager
,
який потрібно підключити, transactionManager
. Якщо бін TransactionManager
,
який тобі потрібно зв'язати, має будь-яке інше ім'я, то потрібно використовувати атрибут
transaction-manager
явно, як це зроблено в попередньому прикладі.
Визначення <aop:config/>
дозволяє транзакційним рекомендаціям, визначеним біном
txAdvice
, гарантовано запускатися у відповідних точках програми. Спочатку визначається зріз,
який приводиться у відповідність до виконання будь-якої операції, визначеної в інтерфейсі FooService(fooServiceOperation
).
Потім зріз прив'язується до txAdvice
за допомогою Advisor. Результат показує, що під час
виконання fooServiceOperation
починає виконуватися Advice, визначений
txAdvice
.
Вираз, визначений в елементі <aop:pointcut/>
, є виразом
зрізів з AspectJ. Більш детальну інформацію про вирази зрізів у Spring дивіться в розділі
Часто необхідно зробити весь рівень служб транзакційним.
Найкращий спосіб зробити це – змінити вираз зрізу так, щоб він відповідав будь-якій операції на рівні служб.
У цьому прикладі показано, як це зробити:
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
x.y.service
. Детальнішу інформацію див. у розділі АОП.
Тепер, коли ми проаналізували конфігурацію, то у вас може виникнути питання: "А що, власне, робить вся ця конфігурація?".
Конфигурація, яку показано раніше, використовується для створення транзакційного проксі навколо об'єкта, який
створюється із визначення біна fooService
. Проксі налаштований з використанням трансакційних Advice
таким чином,
що коли відповідний метод викликається на проксі, транзакція запускається, зупиняється, позначається як
доступна тільки для читання і так далі, залежно від конфігурації транзакції, пов'язаної з цим методом.
Розглянемо наступну програму, яка здійснює тестовий прогін конфігурації, показаної раніше:
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
FooService fooService = ctx.getBean(FooService.class);
fooService.insertFoo(new Foo());
}
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = ClassPathXmlApplicationContext("context.xml")
val fooService = ctx.getBean<FooService>("fooService")
fooService.insertFoo(Foo())
}
Результат виконання попередньої програми повинен виглядати наступним чином (виведення Log4J і трасування стека
від UnsupportedOperationException
, згенерованого методом insertFoo(..)
класу
DefaultFooService
, були усічені для наочності):
<!-- контейнер Spring запускається... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' з 0 спільними інтерпретаторами і 1 specific interceptors
<!-- DefaultFooService фактично проксується -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
<!-- ... метод insertFoo(..) тепер викликається на проксі -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
<!-- тут вступає в дію транзакційний Advice... -->
[DataSourceTransactionManager] - створення нового transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction
<!--- з DefaultFooService генерує виняток... -->
[RuleBasedTransactionAttribute] - Використовуючи правила для визначення, де transaction повинен rollback на java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback для transaction на x.y.service.FooService.insert та транзакція відкочується (за замовчуванням екземпляри RuntimeException призводять до відкату) -->
[DataSourceTransactionManager] - Rolling back JDBC Transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection до DataSource Except .UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- Елементи трасування стека інфраструктури АОП видалені для наочності -->
at $Proxy0.insertFoo(Unknown Source) at Boot.main(Boot.java:11)
Щоб використовувати керування реактивними транзакціями, код повинен використовувати реактивні типи .
ReactiveAdapterRegistry
для визначення того, чи тип методу, що повертається, є реактивним.
У наступному лістингу показана модифікована версія раніше використовуваного FooService
, але
цього разу в коді використовуються реактивні типи:
// інтерфейс реактивного сервісу, який ми хочемо зробити транзакційним
package x.y.service;
public interface FooService {
Flux<Foo> getFoo(String fooName);
Publisher<Foo> getFoo(String fooName, String barName);
Mono<Void> insertFoo(Foo foo);
Mono<Void> updateFoo(Foo foo);
}
// інтерфейс реактивного сервісу, який ми хочемо зробити транзакційним
// the reactive service interface that we want to make transactional
package x.y.service
interface FooService {
fun getFoo(fooName: String): Flow<Foo>
fun getFoo(fooName: String, barName: String): Publisher<Foo>
fun insertFoo(foo: Foo) : Mono<Void>
fun updateFoo(foo: Foo) : Mono<Void>
}
У наступному прикладі показано реалізацію попереднього інтерфейсу:
package x.y.service;
public class DefaultFooService implements FooService {
@Override
public Flux<Foo> getFoo(String fooName) {
// ...
}
@Override
public Publisher<Foo> getFoo(String fooName, String barName) {
// ...
}
@Override
public Mono<Void> insertFoo(Foo foo) {
// ...
}
@Override
public Mono<Void> updateFoo(Foo foo) {
// ...
}
}
package x.y.service
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Flow<Foo> {
// ...
}
override fun getFoo(fooName: String, barName: String): Publisher<Foo> {
// ...
}
override fun insertFoo(foo: Foo): Mono<Void> {
// ...
}
override fun updateFoo(foo: Foo): Mono<Void> {
// ...
}
}
Імперативне та реактивне управління транзакціями мають однакову семантику для визначення меж
транзакцій та атрибутів транзакцій. Основна відмінність між імперативними та реактивними транзакціями
полягає у відкладеному характері останніх. Для початку TransactionInterceptor
декорує
реактивний тип, що повертається, транзакційним оператором і очищує транзакцію. Тому виклик транзакційного
реактивного методу передає фактичне управління транзакціями типу підписки, який активує обробку реактивного типу.
Другий аспект реактивного управління транзакціями пов'язаний з екрануванням даних, яке є природним наслідком моделі програмування. Повертаємі значення методів імперативних транзакцій повертаються з транзакційних методів при успішному завершенні методу, так що частково обчислені результати не виходять із замикання методу. та завершити обчислення.
Publisher
може породжувати дані, доки транзакція виконується, але не обов'язково завершена. Тому
методи, які залежать від успішного завершення всієї транзакції, повинні забезпечувати завершення та
буферизацію результатів у коді, що викликає.
Відкочування декларативної транзакції
У попередньому розділі були викладені основи того, як декларативно встановлювати параметри транзакцій для класів
(зазвичай класів рівня служб) у твоєму додатку. У цьому розділі описано, як керувати відкатом транзакцій
простим, декларативним способом у конфігурації XML. Докладніше про декларативне управління семантикою
відкату за допомогою анотації @Transactional
див. у розділі Параметри
анотації @Transactional
.
Рекомендований спосіб дати команду транзакційній інфраструктурі Spring Framework відкотити роботу транзакції
– це згенерувати Exception
з коду, який наразі виконується у контексті транзакції.
Код транзакційної інфраструктури Spring Framework перехоплює будь-яке необроблене Exception
,
якщо воно піднімається стеком викликів, і визначає, чи потрібно позначати транзакцію для відкату.
У стандартній конфігурації код транзакційної інфраструктури Spring Framework позначає транзакцію для відкату
лише у разі неперевірених винятків під час виконання. Тобто, якщо згенерований виняток є екземпляром або
підкласом RuntimeException
. (Примірники Error
також, за умовчанням, призводять до
відкату). Перевірені винятки, які генеруються з транзакційного методу, не призводять до відкату в
конфігурації за замовчуванням.
Можна налаштувати, які саме типи Exception позначають транзакцію для відкату, включно з винятками, які перевіряються, якщо встановити правила відкату.
Правила відкату визначають, чи потрібно відкочувати транзакцію у разі виникнення певного винятку, і ці
правила ґрунтуються на шаблонах. Шаблон може бути повним ім'ям класу або підрядком повністю уточненого
імені класу для типу винятку (який має бути підкласом Throwable
), без підтримки значень
на цей момент. Наприклад, значення "javax.servlet.ServletException"
або
"ServletException"
співпадатиме з javax.servlet.ServletException
та його підкласами.
Правила відкату можуть бути налаштовані в XML за допомогою атрибутів rollback-for
та
no-rollback-for
, які дозволяють встановлювати шаблони у вигляді рядків. При використанні анотації
@Transactional
правила відкату можна налаштувати за допомогою атрибутів rollbackFor/noRollbackFor
та rollbackForClassName/noRollbackForClassName
,
які дозволяють встановлювати шаблони у вигляді посилань на Class
або рядків відповідно. Якщо тип
винятку вказаний як посилання на клас, то шаблон буде використовувати його повне ім'я. Отже, анотація
@Transactional(rollbackFor = example.CustomException.class)
еквівалентна анотації @Transactional(rollbackForClassName
= "example.CustomException")
.
Тобі потрібно ретельно продумати, наскільки конкретний шаблон і чи
потрібно включати інформацію про пакет (це необов'язково). Наприклад,
Більш того, правила відкату можуть стати причиною ненавмисного збігу для однойменних
винятків та вкладених класів. Це пов'язано з тим, що згенерований виняток вважається
відповідним для цього правила відкату, якщо ім'я згенерованого винятку містить
шаблон винятків, налаштований для цього правила відкату. Наприклад, якщо правило
налаштовано на відповідність |
Наступний фрагмент XML демонструє, як налаштувати відкат для випробуваного, специфічного для програми типу
Exception
шляхом надання шаблону виключення через атрибут rollback-for
:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
Якщо тобі не потрібно, щоб транзакція відкочувалася при генерації виключення, також можна встановити правила "без
відкату". У наступному прикладі інфраструктури транзакцій Spring Framework дається команда зафіксувати
відповідну транзакцію навіть у разі необробленого виключення InstrumentNotFoundException
:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
Якщо інфраструктура транзакцій Spring Framework перехоплює виняток і звертається до налаштованих правил
відкату, щоб визначити, чи відзначати транзакцію для відкату, пріоритетним стає найсуворіше правило. Так, у
разі наступної конфігурації будь-який виняток, крім InstrumentNotFoundException
, призводить до
відкату відповідної транзакції:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>
Ви також можете вказати обов'язковий відкат програмно. Незважаючи на свою простоту, цей процес є досить агресивним і жорстко пов'язує ваш код з інфраструктурою транзакцій Spring Framework. У цьому прикладі показано, як програмно вказати обов'язковий відкат:
public void resolvePosition() {
try {
// деяка бізнес-логіка...
} catch ( NoProductInStockException ex) {
// запускаємо відкат програмно
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
fun resolvePosition() {
try {
// деяка бізнес-логіка...
} catch (ex: NoProductInStockException) {
// запускаємо відкат програмно
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
Рекомендується використовувати декларативний підхід до відкату, якщо це взагалі можливо. Програмний відкат доступний у разі крайньої необхідності, але його використання йде врозріз із принципами чистої архітектури на основі POJO.
Налаштування різної транзакційної семантики для різних бінів
Розглянемо сценарій, в якому є кілька об'єктів рівня служб, і вам потрібно застосувати зовсім різні конфігурації
транзакції до
кожного з них. Це можна зробити, визначивши окремі елементи <aop:advisor/>
з різними
значеннями атрибутів pointcut
та advice-ref
.
Для порівняння спочатку припустимо, що всі класи рівня служб визначені у кореневому пакеті
x.y.service
. Щоб усі біни, які є екземплярами класів, визначених у цьому пакеті (або в
підпакетах) та імена яких закінчуються на Service
, мали стандартну конфігурацію, можна написати
наступне:
<?xml version="1.0" encoding="UTF- 8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http:// www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:pointcut id="serviceOperation"
expression="execution(* x.y.service..*Service.*(..))"/>
<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
</aop:config>
<!-- ці два бина будуть транзакційними... -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<bean id="barService" class="x.y.service.extras.SimpleBarService"/>
<!-- ... а ці два біни – не будуть... -->
<bean id="anotherService" class="org.xyz.SomeService"/>
<!-- (not in the right package) -->
<bean id="barManager" class="x.y.service.SimpleBarManager"/>
<!-- (doesn't end in 'Service') -->
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- інші біни транзакційної інфраструктури, такі як TransactionManager, опущені... -->
</beans>
У наступному прикладі показано, як конфігурувати два різні біни з абсолютно різними транзакційними параметрами:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:pointcut id="defaultServiceOperation"
expression="execution(* x.y.service.*Service.*(..))"/>
<aop:pointcut id="noTxServiceOperation"
expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
<aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
<aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
</aop:config>
<!-- цей бін буде транзакційним (див. зріз "defaultServiceOperation") -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- цей бін також буде транзакційним, але з іншими транзакційними параметрами -->
<bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>
<tx:advice id="defaultTxAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<tx:advice id="noTxAdvice">
<tx:attributes>
<tx:method name="*" propagation="NEVER"/>
</tx:attributes>
</tx:advice>
<!-- інші біни транзакційної інфраструктури, такі як TransactionManager, опущені... -->
</beans>
Параметри <tx:advice/>
У цьому розділі коротко описані різні параметри транзакції, які можна задати за допомогою тега <tx:advice/>
.
Параметри <tx:advice/>
за замовчуванням такі:
Параметр поширення є
REQUIRED
.Рівень ізоляції -
DEFAULT
.Транзакція виконується за принципом "читання-запис".
Час очікування транзакції за замовчуванням дорівнює часу очікування за замовчуванням базової системи транзакцій або відсутня, якщо значення часу очікування не підтримуються. ні.
Ти можеш змінити ці параметри за замовчуванням. У наступній таблиці наведено різні атрибути тегів <tx:method/>
,
які вкладені в теги <tx:advice/>
та <tx: attributes/>
:
Атрибут | Обов'язковий | За замовчуванням | Опис |
---|---|---|---|
|
Так |
Імена методів, з якими мають бути пов'язані атрибути транзакції. Символ підстановки (*) можна
використовувати для того, щоб зв'язати одні й самі параметри атрибутів транзакції з кількома
методами (наприклад, |
|
|
Ні |
|
Логіка роботи при поширенні транзакцій. |
|
Ні |
|
Рівень ізоляції транзакцій. Використовується лише для налаштувань поширення |
|
Ні |
-1 |
Час очікування транзакції (Секунди). Використовується лише для поширення |
|
Ні |
false |
Транзакція для "читання-запису" проти транзакції "лише для читання". Застосовується лише до
|
|
Ні |
Список екземплярів |
|
|
No |
Список екземплярів |
Використання анотації @Transactional
На додаток до декларативного підходу до конфігурації транзакцій на основі XML можна використовувати підхід на основі анотацій. Оголошення семантики транзакцій безпосередньо у вихідному коді Java робить декларації набагато ближче до коду, що згадується. Небезпека надмірної зв'язаності невелика, оскільки код, призначений для транзакційного використання, майже завжди розгортається саме таким чином. Також підтримується як заміна власної анотації Spring. Більш детальну інформацію можна знайти в документації за JTA 1.2.
Простоту використання анотації @Transactional
найкраще проілюструвати прикладом, який
пояснюється в даному тексті. Розглянемо таке визначення класу:
// клас служби, який ми хочемо зробити транзакційним
@Transactional
public class DefaultFooService implements FooService {
@Override
public Foo getFoo(String fooName) {
// ...
}
@Override
public Foo getFoo(String fooName, String barName) {
// ...
}
@Override
public void insertFoo(Foo foo) {
// ...
}
@Override
public void updateFoo(Foo foo) {
// ...
}
}
// клас служби, який ми хочемо зробити транзакційним
@Transactional
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Foo {
// ...
}
override fun getFoo(fooName: String, barName: String): Foo {
// ...
}
override fun insertFoo(foo: Foo) {
// ...
}
override fun updateFoo(foo: Foo) {
// ...
}
}
Використовувана на рівні класу анотація, як описано вище, вказує значення за замовчуванням для всіх методів
класу, що оголошує (а також його підкласів). Крім того, кожен метод можна анотувати окремо. розділ "Видимість
методу та анотація @Transactional
" для отримання більш детальної інформації про те, які
методи Spring вважає транзакційними. Зверни увагу, що анотацыя на рівні класу не поширюється на класи-предки,
розташовані вище за ієрархією класів; у цьому випадку успадковані методи повинні бути локально
переоголошені, щоб брати участь в анотації на рівні підкласів. Якщо POJO-клас, подібний до наведеного вище,
визначено як бін у середовищі Spring, можна зробити екземпляр біна транзакційним за допомогою анотації @EnableTransactionManagement
у
класі, анотованому @Configuration
. Детальнішу інформацію див.javadoc.
У конфігурації XML тег <tx:annotation-driven/>
забезпечує аналогічний механізм:
<!-- з файлу "context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi :schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd ">
<!-- це об'єкт-служба, який ми хочемо зробити транзакційним -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- активуємо конфігурацію логіки роботи транзакцій на основі анотацій -->
<!-- TransactionManager, як і раніше, необхідний -->
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- (ця залежність визначається в іншому місці) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- інші визначення <bean/--> -->
</beans>
- Рядок, який робить екземпляр біна транзакційним.
transaction-manager
у тезі <tx:annotation-driven/>
,
якщо бін TransactionManager
, який nи хочеi підключити, іменований як transactionManager
.
Якщо бін TransactionManager
, який тобі потрібно впровадити, має будь-яке інше ім'я, необхідно
використовувати атрибут transaction-manager
, як у попередньому прикладі.
Реактивні транзакційні методи використовують реактивні типи повернення на відміну від імперативних механізмів програмування, як показано в наступному лістингу:
// клас реактивної служби, який ми хочемо зробити транзакційним
@Transactional
public class DefaultFooService implements FooService {
@Override
public Publisher<Foo> getFoo(String fooName) {
// ...
}
@Override
public Mono<Foo> getFoo(String fooName, String barName) {
// ...
}
@Override
public Mono<Void> insertFoo(Foo foo) {
// ...
}
@Override
public Mono<Void> updateFoo(Foo foo) {
// ...
}
}
// клас реактивної служби, який ми хочемо зробити транзакційним
@Transactional
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Flow<Foo> {
// ...
}
override fun getFoo(fooName: String, barName: String): Mono<Foo> {
// ...
}
override fun insertFoo(foo: Foo): Mono<Void> {
// ...
}
override fun updateFoo(foo: Foo): Mono<Void> {
// ...
}
}
Зверни увагу, що для повертаємого Publisher
існують особливі міркування щодо сигналів
скасування Reactive Streams. Докладніше див. підрозділ "Сигнали
скасування" у розділі "Використання TransactionalOperator".
@Transactional
Якщо ти використовуєш транзакційні проксі зі стандартною конфігурацією Spring, то анотацію
@Transactional
потрібно застосовувати лише до методів з public
видимістю. Якщо ти позначиш protected
,
private
методи або методи з областю видимості в межах пакета анотацією
@Transactional
, помилки не виникне, але анотований метод не видасть конфігурованих
транзакційних параметрів. Якщо потрібно анотувати непублічні методи, ознайомся з підказкою в
наступному абзаці, що описує проксі на основі класів, або використовуй зв'язування під час
завантаження чи компіляції на основі AspectJ (буде описано далі).
При використанні анотації @EnableTransactionManagement
у класі, позначеному анотацією
@Configuration
, protected
методи або методи з областю видимості в межах пакета
також можна зробити транзакційними для проксі на основі класу, зареєструвавши кастомний бін transactionAttributeSource
,
як показано в прикладі далі. Зверни увагу, однак, що транзакційні методи в проксі на основі інтерфейсу
завжди повинні бути public
і визначені в проксованому інтерфейсі.
/**
* Зареєструй кастомний AnnotationTransactionAttributeSource з використанням
* прапора publicMethodsOnly, встановленим у false, щоб активувати підтримку
* захищених методів та методів з областю видимості в межах пакета,
* позначені анотацією @Transactional,
* проксі на основі класів.
*
* @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
*/ @Bean
TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(false);
}
Spring TestContext Framework за замовчуванням підтримує незакриті методи тестування, анотовані
@Transactional
. Приклади див. у розділі "Керування
транзакціями" у розділі про тестування.
Аннотацію @Transactional
можна застосувати до визначення інтерфейсу, методу інтерфейсу,
визначення класу або методу класу. Однак лише наявності анотації @Transactional
недостатньо для
активації транзакційної логіки роботи. Анотація @Transactional
– це просто метадані, які можуть
бути використані деякою інфраструктурою часу виконання, яка підтримує анотацію @Transactional
і
може використовувати метадані для конфігурування відповідних бінів з транзакційною логікою роботи. У
попередньому прикладі елемент <tx:annotation-driven/>
перемикає транзакційну логіку
роботи.
@Transactional
лише конкретні класи (і методи конкретних класів), а не анотувати інтерфейси.
Ти, звісно, можеш позначити анотацією @Transactional
інтерфейс (або метод інтерфейсу), але
тільки це буде працювати так, як зазвичай працює, якщо використовувати проксі на основі інтерфейсу. Той
факт, що анотації Java не успадковуються від інтерфейсів, означає, що якщо використовувати проксі на основі
класу (proxy-target-class="true"
) або аспект на основі зв'язування (mode="
aspectj"
), параметри транзакції не будуть розпізнаватись інфраструктурою проксування та
зв'язування, а об'єкт не буде обернутий у транзакційний проксі.
@Transactional
. Крім того,
проксі має бути повністю ініціалізований, щоб забезпечити очікувану логіку роботи, тому не варто покладатися
на цю функцію в ініціалізаційному коді – наприклад, у методі, анотованому @PostConstruct
.
Розглянь можливість використання режиму AspectJ (див. атрибут mode
у наступній таблиці), якщо
очікується, що самовиклики також будуть обернуті транзакціями. В цьому випадку проксі взагалі буде далеко не
на першому місці. Натомість цільовий клас змінюється (тобто його байт-код модифікується), щоб почати
підтримувати логіку роботи анотації @Transactional
під час виконання для методу будь-якого
виду.
Атрибут XML | Атрибут анотації | За умовчанням | Опис |
---|---|---|---|
|
Н/Д (див. javadoc по |
|
Ім'я диспетчера транзакцій, що використовується. Обов'язковий, тільки якщо ім'я диспетчера
транзакцій не |
|
|
|
Режим за замовчуванням ( |
|
|
|
Застосовується тільки в режимі |
|
|
|
Визначає порядок трансакційних Advice, які застосовуються до бінів, помічених анотацією |
@Transactional
— proxy
, що дозволяє перехоплювати дзвінки лише через проксі. Локальні виклики в межах одного класу
перехопити так само не можна.
Для більш розширеного режиму перехоплення розглянь можливість переходу на режим aspectj
у поєднанні зі
зв'язуванням під час компіляції або під час завантаження.
proxy-target-class
керує тим, який тип транзакційного
проксі створюється для класів,
анотованих @Transactional
. Якщо proxy-target-class
має значення true
,
проксі створюються на основі класів. Якщо proxy-target-class
має значення false
або
опущено атрибут, створюються стандартні проксі на основі інтерфейсу JDK. (Опис різних типів проксі див. у
розділі "Механізми проксування").
@EnableTransactionManagement
та <tx:annotation-driven/>
шукають @Transactional
тільки для бінів у тому самому контексті додатку, в якому вони
визначені. Це означає, що якщо ви помістите конфігурацію, керовану анотаціями, до
WebApplicationContext
для DispatcherServlet
, він перевірятиме наявність бінів з
анотацією @Transactional
тільки у ваших контролерів, але не у ваших службах. Докладнішу
інформацію див. у розділі, присвяченому MVC.
Під час обчислення транзакційних параметрів методу пріоритет має найбільш похідне розташування. У наступному
прикладі клас DefaultFooService
анотований на рівні класу з налаштуваннями транзакції "лише
для читання", але анотація @Transactional
на методі updateFoo(Foo)
в тому ж класі
має пріоритет над налаштуваннями транзакції, визначеними на рівні класу.
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo( String fooName) {
// ...
}
// ці налаштування пріоритетніші для цього методу
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// ...
}
}
@Transactional(readOnly = true)
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Foo {
// ...
}
// ці налаштування пріоритетніші для цього методу
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
override fun updateFoo(foo: Foo) {
// ...
}
}
Параметри анотації @Transactional
Анотація @Transactional
– це метадані, які вказують, що інтерфейс, клас або метод повинен мати
транзакційну семантику (наприклад, "при виклику цього методу починати абсолютно нову транзакцію в режимі
"тільки для читання", зупиняючи всі існуючі транзакції"). Параметри @Transactional
за
замовчуванням такі:
Параметром розповсюдження є
PROPAGATION_REQUIRED
.Рівень ізоляції —
ISOLATION_DEFAULT
.Транзакція виконується за принципом "читання-запис".
Час очікування транзакції за замовчуванням дорівнює часу очікування за замовчуванням основної системи транзакцій, або не дорівнює, якщо значення часу очікування не підтримуються.
Будь-які
RuntimeException
абоError
викликають відкат, а будь-який перевіренийException
— ні.
Ти можеш змінити ці параметри за замовчуванням. У наступній таблиці наведено різні властивості анотації
@Transactional
:
Властивість | Тип | Опис |
---|---|---|
|
|
Необов'язковий кваліфікатор, який визначає менеджер транзакцій, що використовується. |
|
|
Псевдонім для |
|
Масив |
Мітки можуть бути обчислені диспетчерами транзакцій, щоб пов'язати специфічну для реалізації логіку роботи з фактичною транзакцією. |
|
Додатковий параметр розповсюдження. |
|
|
|
Додатковий рівень ізоляції. Застосовується лише до значень поширення |
|
|
Необов'язковий час очікування транзакції. Застосовується лише до значень поширення
|
|
|
Альтернатива для завдання |
|
|
Транзакція в режимі "читання-запис" проти транзакції в режимі "лише читання". Застосовується
лише до значень |
|
Масив об'єктів |
Необов'язковий масив типів винятків, які мають викликати відкат. |
|
Масив шаблонів імен винятків. |
Необов'язковий масив шаблонів імен винятків, які мають викликати відкат. |
|
Масив об'єктів |
Необов'язковий масив типів винятків, які не повинні викликати відкат. |
|
Масив шаблонів імен винятків. |
Необов'язковий масив шаблонів імен винятків, які не повинні викликати відкат. |
Наразі не можна явно керувати ім'ям транзакції, де "ім'я" означає ім'я транзакції, яке відображається в
моніторі транзакцій, якщо це застосовно (наприклад, монітор транзакцій WebLogic), і в журнальних вихідних
даних. Для декларативних транзакцій ім'ям транзакції завжди є повністю уточнене ім'я класу + .
+ ім'я методу транзакційно обладнаного Advice класом. Наприклад, якщо метод handlePayment(..)
класу BusinessService
запускає транзакцію, іменем транзакції буде: com.example.BusinessService.handlePayment
.
Використання кількох диспетчерів транзакцій за допомогою анотації @Transactional
Більшості додатків Spring потрібен лише один диспетчер транзакцій, але можуть виникнути ситуації, коли
потрібно кілька незалежних диспетчерів транзакцій в одному додатку. Можна використовувати
value
або атрибут transactionManager
анотації @Transactional
, щоб
опційно встановити ідентифікатор використовуваного TransactionManager
. Це може бути ім'я біна, або
значення кваліфікатора біна диспетчера транзакцій. Наприклад, використовуючи нотацію кваліфікатора, можна
об'єднати нижченаведений код Java з наведеними нижче оголошеннями бінів диспетчера транзакцій у контексті
програми:
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
@Transactional("reactive-account")
public Mono<Void> doSomethingReactive() { ... }
}
class TransactionalService {
@Transactional("order")
fun setSomething(name: String) {
// ...
}
@Transactional("account")
fun doSomething() {
// ...
}
@Transactional("reactive-account")
fun doSomethingReactive(): Mono<Void> {
// ...
}
}
У наступному лістингу показані оголошення бінів:
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="order"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="account"/>
</bean>
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
...
<qualifier value="reactive-account"/>
</bean>
У цьому випадку окремі методи для TransactionalService
працюють під управлінням окремих
диспетчерів транзакцій, що відрізняються за кваліфікаторами order
, account
та
reactive-account
. Ім'я цільового біну за замовчуванням <tx:annotation-driven>
,
transactionManager
все ще використовується, якщо не вдається знайти спеціально уточнений бін
TransactionManager
.
Кастомні складені анотації
Якщо виявляється, що одні й самі атрибути з анотацією @Transactional
неодноразово
використовуються в багатьох різних методах, підтримка мета-анотацій у
Spring дозволить визначити кастомні складові анотації для конкретних випадків використання. Як
приклад розглянемо таке визначення анотації:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "order", label = ["causal-consistency"])
annotation class OrderTx
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "account", label = ["retryable"])
annotation class AccountTx
Попередні анотації дозволяють записати приклад з попереднього розділу таким чином:
public class TransactionalService {
@OrderTx
public void setSomething(String name) {
// ...
}
@AccountTx
public void doSomething() {
// ...
}
}
class TransactionalService {
@OrderTx
fun setSomething(name: String) {
// ...
}
@AccountTx
fun doSomething() {
// ...
}
}
У попередньому прикладі ми використовували синтаксис для визначення кваліфікатора диспетчера транзакцій та транзакційних міток, але ми також могли б додати логіку роботи розповсюдження, правила відкату, час очікування та інші функції. у Spring. Зверни увагу, що цей розділ не є належним вступом до теми розповсюдження транзакцій. Скоріше, тут детально описана семантика поширення транзакцій у Spring.
У разі транзакцій, керованих Spring, пам'ятай про різницю між фізичними та логічними транзакціями, а також про те, як параметр поширення застосовується з урахуванням цієї відмінності.
Understanding PROPAGATION_REQUIRED
PROPAGATION_REQUIRED
забезпечує виконання фізичної транзакції або локально доступності, якщо
транзакція ще не існує, або шляхом присутності у наявної "зовнішньої" транзакції, визначеної більшої області
доступності. Це цілком прийнятне значення за замовчуванням при звичайній організації стеку викликів в одному
потоці (наприклад, фасад служби, який делегує повноваження кільком методам репозиторію, де всі базові
ресурси повинні бути присутніми в транзакції на рівні служб).
validateExistingTransactions
у true
для вашого диспетчера транзакцій, якщо хочеш, щоб оголошення рівня ізоляції були
недоступні за участі у існуючій транзакції з іншим рівнем ізоляції. Цей режим також відхиляє невідповідність
режиму "лише для читання" (тобто якщо внутрішня транзакція в режимі "читання-запису" намагається увійти до
зовнішньої області доступності режиму "лише для читання").
Якщо параметр розповсюдження має значення PROPAGATION_REQUIRED
для кожного методу, до якого
застосовується цей параметр, створюється область логічних транзакцій. Кожна така область логічних транзакцій
може визначати статус "тільки відкат" індивідуально, причому область зовнішніх транзакцій логічно незалежна
від області внутрішніх транзакцій. У разі стандартної логіки роботи PROPAGATION_REQUIRED
всі ці
області доступності відображаються на одну й ту саму фізичну транзакцію. Таким чином, маркер "тільки
відкат", встановлений в області доступності внутрішніх транзакцій, впливає на можливість зовнішньої
транзакції виконати фіксацію. транзакція самостійно не приймає рішення про відкат, тому відкат (мовчки
викликаний областю доступності внутрішніх транзакцій) є непередбаченим. У цей момент генерується відповідний
виняток UnexpectedRollbackException
. Це очікувана логіка роботи, тому код, що викликає
транзакцію, не може спрацювати невірно, прийнявши, що фіксація була виконана, хоча насправді цього не було.
Таким чином, якщо внутрішня транзакція (про яку зовнішній код не знає) мовчки позначить транзакцію як ту, що
підлягає відкату, зовнішній код, що викликає, все одно викличе фіксацію. Зовнішній викликаючий код повинен
отримати UnexpectedRollbackException
, щоб чітко вказати, що замість цього було виконано відкат.
Основні відомості про PROPAGATION_REQUIRES_NEW
PROPAGATION_REQUIRES_NEW
, на відміну від PROPAGATION_REQUIRED
, завжди використовує
незалежну фізичну транзакцію для кожної області доступності транзакцій, що зачіпається, ніколи не беручи
участі в існуючій області. У такій схемі
транзакції, що лежать в основі ресурсу, різняться і, отже, можуть фіксуватися або відкочуватися незалежно
один від одного, причому на зовнішню транзакцію не впливає статус відкату внутрішньої транзакції, а
блокування внутрішньої транзакції знімаються відразу після її завершення. Така незалежна внутрішня
транзакція може також оголосити свій власний рівень ізоляції, час очікування та параметри "тільки читання" і
не наслідувати характеристики зовнішньої транзакції.
Основні відомості про PROPAGATION_NESTED
PROPAGATION_NESTED
використовує
одну фізичну транзакцію з кількома точками збереження, на які можна відкотитися. Такі часткові відкати
дозволяють області доступності внутрішніх транзакцій ініціювати відкат своєї області доступності, водночас
зовнішня транзакція може продовжувати фізичну транзакцію, незважаючи на те, що деякі операції були відкочені.
Цей параметр зазвичай відображається на точках збереження JDBC, тому працює тільки з транзакціями ресурсів JDBC.
Див.
DataSourceTransactionManager
з Spring.
Постачання транзакційних операцій порадами
Припустимо, тобі потрібно виконувати як транзакційні операції, так і деякі базові профільні Advice. Як це
можна зробити в контексті <tx:annotation-driven/>
?
Якщо викликається метод updateFoo(Foo)
, то можна очікувати наступних дій:
Запускається конфігурований профільуючий аспект.
Починає виконуватися транзакційний Advice.
Починає виконуватися метод для обладнаного Advice-ом об'єкта.
Транзакція фіксується.
Профілюючий аспект повідомляє точну тривалість всього виклику транзакційного методу. У цьому розділі ми особливо не торкаємося АОП (за винятком його застосування до транзакцій). Дивіться розділ АОП для ознайомлення з детальним освітленням АОП-конфігурації та АОП в цілому.
У наступному коді показаний простий профільуючий аспект, розглянутий раніше:
package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;
public class SimpleProfiler implements Ordered {
private int order;
// дозволяється контролювати відповідність громадського
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
// Цей мето - Advice "instead of"
public Object profile(ProceedingJoinPoint call) throws Throwable {
Object returnValue;
StopWatch clock = new StopWatch(getClass().getName());
try {
clock.start(call.toShortString());
returnValue = call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
return returnValue;
}
}
class SimpleProfiler : Ordered {
private var order: Int = 0
// дозволяється контролювати відповідність громадського
override fun getOrder(): Int {
return this.order
}
fun setOrder(order: Int) {
this.order = order
}
// Цей мето - Advice "instead of"
fun profile(call: ProceedingJoinPoint): Any {
var returnValue: Any
val clock = StopWatch(javaClass.name)
try {
clock.start(call.toShortString())
returnValue = call.proceed()
} finally {
clock.stop()
println(clock.prettyPrint())
} return returnValue
}
}
Упорядкування Advice керується за допомогою інтерфейсу Ordered
. Докладнішу інформацію
про впорядкування Advice див. у розділі "Упорядкування
Advice".
Наступна конфігурація створює бін fooService
, до якого в потрібному порядку
застосовуються профільні та транзакційні аспекти:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- це аспект -->
<bean id="profiler" class="x.y.SimpleProfiler">
<!-- виконується до транзакційного Advice (тому порядковий номер нижче) -->
<property name="order" value="1"/>
</bean>
<tx:annotation-driven transaction-manager="txManager" order="200"/>
<aop:config>
<!-- цей Advice виконується замість транзакційного Advice... -->
<aop:aspect id="profilingAspect" ref="profiler">
<aop:pointcut id="serviceMethodWithReturnValue"
expression="execution(!void x.y..*Service.*(..))"/>
<aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
</aop:aspect>
</aop:config>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
Аналогічним чином можна налаштувати будь-яку кількість додаткових аспектів.
У наступному прикладі створено той самий набір заданих значень, що й у попередніх двох прикладах, але використовується суто декларативний підхід XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- профільуючий Advice -->
<bean id="profiler" class="x.y.SimpleProfiler">
<!-- виконується до транзакційного Advice (тому порядковий номер нижче) -->
<property name="order" value="1"/>
</bean>
<aop:config>
<aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
<!-- запускається після профілюючого Advice (пор. атрибут черговості) -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
<!-- значення черговості вище, ніж у профілюючого аспекту -->
<aop:aspect id="profilingAspect" ref="profiler">
<aop:pointcut id="serviceMethodWithReturnValue"
expression="execution(!void x.y..*Service.*(..))"/>
<aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
</aop:aspect>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- інші визначення <bean/> такі як DataSource та TransactionManager -->
</beans>
Результатом попередньої конфігурації є бін fooService
, до якого в такому порядку
застосовуються профільні та транзакційні аспекти. Якщо потрібно, щоб профілюючий Advice виконувався
після транзакційного Advice на вході та перед транзакційним Advice на виході, можна змінити
значення властивості order
бина профілюючого аспекту таким чином, щоб воно було вищим
за значення черговості транзакційного Advice.
Аналогічним чином можна налаштувати додаткові аспекти.
Використання анотації @Transactional
за допомогою AspectJ
Ти також можеш використовувати засоби підтримки анотації @Transactional
з Spring
Framework поза контейнером Spring за допомогою аспекту AspectJ. Для цього спочатку познач свої
класи (і, за бажанням, методи класів) анотацією @Transactional
, а потім прив'яжи свою
програму до org.springframework.transaction.aspectj.AnnotationTransactionAspect
,
визначеним у файлі spring-aspects.jar
. Тобі також потрібно налаштувати аспект за
допомогою диспетчера транзакцій. Ти можеш використовувати IoC-контейнер Spring Framework для
впровадження залежності в аспект. Найпростіший спосіб конфігурувати аспект керування транзакціями –
використовувати елемент <tx:annotation-driven/>
і вказати атрибут
mode
для aspectj
, як описано у розділі "Використання
анотації
@Transactional
". Оскільки ми фокусуємося на додатках, які виконуються поза
контейнером Spring, то покажемо, як зробити це програмно.
У наступному прикладі показано, як створити диспетчер транзакцій та налаштувати
AnnotationTransactionAspect
на його використання:
// конструюємо відповідний диспетчер транзакцій
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());
// конфігуруємо AnnotationTransactionAspect на його використання; це потрібно зробити перед виконанням будь-яких транзакційних методів
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
// конструюємо відповідний диспетчер транзакцій
val txManager = DataSourceTransactionManager(getDataSource())
// конфігуруємо AnnotationTransactionAspect на його використання; це необхідно зробити перед виконанням будь-яких транзакційних методів
AnnotationTransactionAspect.aspectOf().transactionManager = txManager використання даного аспекту необхідно анотувати клас реалізації (або методи всередині цього класу, або й те, й інше), а не інтерфейс (якщо є), який реалізується цим класом. AspectJ дотримується правила Java щодо того, що анотації для інтерфейсів не успадковуються.
Анотація @Transactional
для класу визначає семантику транзакцій за замовчуванням для
виконання будь-якого публічного методу в класі.
Анотація @Transactional
для методу в класі перевизначає семантику транзакцій за
замовчуванням, встановлену анотацією класу (якщо вона присутня). Ти можеш анотувати будь-який метод,
незалежно від його видимості.
Щоб зв'язати свої програми з AnnotationTransactionAspect
, потрібно або зібрати свою
програму за допомогою AspectJ (див. "Посібник
з розробки на AspectJ"), або використовувати зв'язування під час завантаження.Див. розділ "Зв'язування під
час завантаження за допомогою AspectJ у Spring Framework" для ознайомлення з принципами
зв'язування під час завантаження з за допомогою AspectJ.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ