Більшість користувачів Spring Framework вибирають декларативне управління транзакціями. Цей варіант має найменший вплив на код програми і, отже, найбільш відповідає ідеї неагресивного полегшеного контейнера. Однак, оскільки код транзакційних аспектів поширюється разом з дистрибутивом Spring Framework і може бути використаний у вигляді шаблону, для ефективного використання цього коду зазвичай не потрібно розуміння концепцій АОП. , оскільки можна визначити логіку роботи транзакцій (чи її відсутність) до рівня окремих методів. За потреби можна здійснити виклик setRollbackOnly() у контексті транзакції. Відмінності між двома типами управління транзакціями полягають у наступному:

  • На відміну від CMT з EJB, який прив'язаний до JTA, декларативне управління транзакціями у Spring Framework працює у будь-якому оточенні. Він може працювати з транзакціями JTA або локальними транзакціями за допомогою JDBC, JPA або Hibernate шляхом коригування конфігураційних файлів. до спеціальних класів, таких як класи EJB.

  • Spring Framework пропонує декларативні правила відкату, які не мають аналогів у EJB. Передбачено як програмну, так і декларативну підтримку правил відкату.

  • Spring Framework дозволяє налаштовувати логіку роботи транзакцій за допомогою АОП. Наприклад, можна впровадити кастомну логіку роботи у разі відкату транзакції. Також можна додавати довільні Advice поряд із транзакційними Advice. При використанні CMT з EJB не можна впливати на керування транзакціями контейнера, крім за допомогою setRollbackOnly().

  • Spring Framework не підтримує поширення контекстів транзакцій через віддалені дзвінки, як це роблять сервери додатків вищого класу. Якщо потрібна ця функція, ми рекомендуємо використовувати EJB. Однак добре подумайте, перш ніж використовувати таку функцію, тому що, як правило, транзакції не повинні охоплюватися віддаленими викликами. Концепція правил відкату дуже важлива. Ці правила дозволяють задати, які винятки (і події, що викидаються) повинні призводити до автоматичного відкату. Ви можете поставити це декларативно, у конфігурації, а не в коді Java. Тому, хоча ви все ще можете викликати setRollbackOnly() для об'єкта TransactionStatus, щоб відкотити транзакцію, що ткає назад, найчастіше краще задати правило, згідно з яким виняток MyApplicationExceptionException завжди має призводити до відкату. Істотною перевагою цього варіанта є те, що бізнес-об'єкти не залежатимуть від транзакційної інфраструктури. Наприклад, вони зазвичай не вимагають імпортування API-інтерфейсів транзакцій Spring чи інших API-інтерфейсів Spring. відкочує транзакцію автоматично при виключенні програми (тобто винятку, що перевіряється, відмінному від java.rmi.RemoteException). Хоча логіка роботи Spring за умовчанням для декларативного управління транзакціями слідує угоді EJB (відкат відбувається автоматично лише за неперевірених винятків), часто буває корисно налаштувати цю логіку роботи.

  • Мало просто сказати, що потрібно помітити класи інструкцією @Transactional, додати інструкцію @EnableTransactionManagement у вашу конфігурацію і чекати, що ви зрозумієте, як все це працює. Щоб дати більш глибоке розуміння, в цьому розділі описані внутрішні механізми декларативної транзакційної інфраструктури Spring Framework у контексті питань, пов'язаних з транзакціями. тому, що ця підтримка забезпечується завдяки проксі АОП і що транзакційні Advice керуються метаданими (в даний час на основі XML або анотацій). Поєднання АОП з транзакційними метаданими дає проксі АОП, який задіює TransactionInterceptor у поєднанні з відповідною реалізацією TransactionManager для керування транзакціями навколо викликів методів.

    АОП у Spring розглянуто в розділі розділі АОП.

    TransactionInterceptor у Spring Framework забезпечує управління транзакціями для імперативних та реактивних моделей програмування. Перехоплювач визначає бажаний тип управління транзакціями, перевіряючи тип методу, що повертається. Методи, що повертають реактивний тип, такий як Publisher або Flow (або їх підтип) мовою Kotlin, підходять для керування реактивними транзакціями. Всі інші типи, що повертаються, включаючи void, використовують шлях виконання коду для імперативного управління транзакціями.

    Особливості управління транзакціями впливають на те, який диспетчер транзакцій потрібен. Імперативні транзакції вимагають PlatformTransactionManager, а реактивні транзакції використовують реалізацію ReactiveTransactionManager.

    Анотація @Transactional зазвичай працює з прив'язаними до потоку транзакціями, керованими PlatformTransactionManager, відкриваючи транзакцію для всіх операцій доступу до даних у поточному виконуваному потоці. Примітка: це не поширюється на знову запущені потоки всередині методу.

    Реактивна транзакція, керована ReactiveTransactionManager, використовує контекст із Reactor замість локальних атрибутів потоку. Як наслідок, всі операції доступу до даних, що беруть участь, повинні виконуватися в одному і тому ж контексті з Reactor в тому самому реактивному конвеєрі.

    На наступному малюнку показано концептуальне подання виклику методу для транзакційного проксі:

    Приклад реалізації декларативної транзакції

    Розглянемо наступний інтерфейс та його відповідну реалізацію. У цьому прикладі класи Foo та Bar використовуються як плейсхолдери, щоб ви могли зосередитися на використанні транзакцій, не відволікаючись на конкретну модель предметної області. Для цілей цього прикладу той факт, що клас DefaultFooService генерує екземпляри UnsupportedOperationException у тілі кожного реалізованого методу, є позитивним. Така логіка роботи дозволяє зрозуміти, як утворюються транзакції, а потім відкочуються у відповідь на екземпляр UnsupportedOperationException. У наступному лістингу показаний інтерфейс FooService:

    Java
    // інтерфейс служби, який ми хочемо зробити транзакційним 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); }
    Kotlin
    // інтерфейс служби, який ми хочемо зробити транзакційним 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) }

    У наступному прикладі показано реалізацію попереднього інтерфейсу:

    Java
    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) { // ... } }
    Kotlin
    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()); } }