Ключем до розуміння абстракції транзакцій Spring є поняття транзакційної стратегії. Транзакційна стратегія визначається TransactionManager, зокрема інтерфейсом org.springframework.transaction.PlatformTransactionManager — для управління імперативними транзакціями, а також інтерфейсом org.springframework.transaction.ReactiveTransactionManager. У наступному лістингу показано визначення API-інтерфейсу PlatformTransactionManager:


public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}

Це в першу чергу інтерфейс постачальника послуг (SPI), хоча його можна використовувати програмно з коду програми. Оскільки PlatformTransactionManager є інтерфейсом, його можна легко реалізувати як об'єкт-імітацію або функції-заглушки в разі необхідності. Він не прив'язаний до стратегії пошуку, як-от JNDI. Реалізації PlatformTransactionManager визначаються як будь-який інший об'єкт (або бін) в IoC-контейнері Spring Framework. Одна ця перевага вже робить транзакції зі Spring Framework гідною абстракцією, навіть якщо ти працюєш з JTA. Тестувати транзакційний код стає набагато простіше, ніж якби він використовував JTA напряму.

Знову ж таки, згідно з філософією Spring, винятон TransactionException, який може бути згенерований будь-яким з методів інтерфейсу PlatformTransactionManager є неперевіреним (тобто розширює клас java.lang.RuntimeException). Збої інфраструктури транзакцій майже завжди критичні. У поодиноких випадках, коли код програми дійсно може відновитися після збою транзакції, розробник програми все ж таки може здійснювати перехоплення та обробку TransactionException. Важливим моментом є те, що розробників можуть робити це без примусу.

Метод getTransaction(..) повертає об'єкт TransactionStatus залежно від TransactionDefinition. Повертаний TransactionStatus може представляти нову транзакцію або може представляти існуючу транзакцію, якщо відповідна транзакція існує в поточному стеку викликів. В останньому випадку, як і у випадку з транзакційними контекстами Java EE, TransactionStatus асоціюється з потоком виконання. Починаючи з версії Spring Framework 5.2, Spring також надає абстракцію управління транзакціями для реактивних додатків, що використовують реактивні типи або співпрограм Kotlin. У наступному лістингу показано транзакційну стратегію, визначену org.springframework.transaction.ReactiveTransactionManager:


public interface ReactiveTransactionManager extends TransactionManager {
    Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;
    Mono<Void> commit(ReactiveTransaction status) throws TransactionException;
    Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}

Інтерфейс TransactionDefinition визначає:

  • Поширення: Зазвичай весь код в області доступності транзакції виконується в цій транзакції. Однак можна вказати логіку роботи у випадках, якщо транзакційний метод запускається, коли вже існує транзакційний контекст. Наприклад, виконання коду може продовжитися в існуючій транзакції (звичайний випадок) або існуюча транзакція може бути призупинена і створена нова транзакція. Spring пропонує будь-які варіанти розповсюдження транзакцій, знайомі CMT з EJB. Для ознайомлення з семантикою поширення транзакцій у Spring див. розділ "Поширення транзакцій".

  • Ізоляція: Ступінь, в якій ця транзакція ізольована від роботи інших транзакцій. Наприклад, чи може ця транзакція бачити незафіксовані записи від інших транзакцій?

  • Час очікування: Тривалість виконання транзакції до її завершення та автоматичного відкату базовою інфраструктурою транзакції.

  • Статус "тільки для читання": Ти можеш використовувати транзакцію в режимі "тільки для читання", коли код читатиметься, але не зможе змінювати дані. Транзакції "тільки для читання" можуть бути корисними в контексті оптимізації в деяких випадках, наприклад, якщо ти використовуєш Hibernate.

Ці параметри відображають стандартні концепції транзакцій. У разі потреби звернися до джерел, в яких розглянуто рівні ізоляції транзакцій та інші основні концепції транзакцій. Розуміння цих концепцій необхідне для використання Spring Framework або будь-якого рішення для управління транзакціями.

Інтерфейс TransactionStatus дозволяє транзакційному коду без жодних перешкод контролювати виконання транзакції та робити запит на статус транзакції. Концепції повинні бути знайомі, оскільки вони є спільними для всіх інтерфейсів API транзакцій. У наступному лістингу показаний інтерфейс TransactionStatus:


public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    @Override
    boolean isNewTransaction();
    boolean hasSavepoint();
    @Override
    void setRollbackOnly();
    @Override
    boolean isRollbackOnly();
    void flush();
    @Override
    boolean isCompleted();
}

Незалежно від того, який спосіб управління транзакціями у Spring ти обереш — декларативний чи програмний — визначення правильної реалізації TransactionManager є абсолютно необхідним. Зазвичай ця реалізація визначається через використання залежностей.

Реалізації TransactionManager вимагають знання оточення, в якому вони працюють: JDBC, JTA, Hibernate тощо. У таких прикладах показано, як можна визначити локальну реалізацію PlatformTransactionManager (в цьому випадку за допомогою звичайного JDBC).

Ти можеш визначити DataSource JDBC, створивши бін, схожий на наступний:


<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

Визначення пов'язаного біна PlatformTransactionManager потім забезпечується посиланням на визначення DataSource. Визначення повинне мати вигляд, схожий на той, що представлений в наступному прикладі:


<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

Якщо ти використовуєш JTA в контейнері Java EE, ти задіюєш контейнерний DataSource, отриманий через JNDI, у поєднанні з JtaTransactionManager зі Spring. У наступному прикладі показано, як виглядатиме версія пошуку через JTA та JNDI:


<?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:jee="http://www.springframework.org/schema/jee"
                   xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        https://www.springframework.org/schema/jee/spring-jee.xsd">
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
    <!-- other definitions <bean/--> -->
</beans><

JtaTransactionManager не потрібно знати про DataSource (або будь-які інші специфічні ресурси), оскільки він використовує глобальну інфраструктуру управління транзакціями контейнера.

У попередньому визначенні біна dataSource використовується тег < jndi-lookup/> з простору імен jee. Для отримання додаткової інформації див. розділ "Схема JEE".
Якщо ти використовуєш JTA, визначення диспетчера транзакцій має виглядати однаково, незалежно від того, яка технологія доступу до даних використовується, будь то JDBC, Hibernate через JPA або будь-яка інша технологія, що підтримується. Це пов'язано з тим, що транзакції JTA є глобальними транзакціями, які можуть задіяти будь-який транзакційний ресурс.

За будь-якого набору встановлених значень для транзакцій Spring код програми не потрібно змінювати. Можна змінити спосіб керування транзакціями, просто змінивши конфігурацію, навіть якщо ця зміна означає перехід від локальних транзакцій до глобальних або навпаки.

Настройка транзакций Hibernate

Також можна легко використовувати локальні транзакції з Hibernate, як буде показано в наступних прикладах. У цьому випадку необхідно визначити LocalSessionFactoryBean для Hibernate, який код твоєї програми може використовувати для отримання екземплярів Session з Hibernate.

Визначення біна DataSource схоже на приклад локального JDBC, продемонстрований раніше, і тому не показано в наступному прикладі.

Якщо пошук джерела даних DataSource (використовується будь-яким не-JTA диспетчером транзакцій) здійснюється через JNDI, а управління відбувається за допомогою Java EE, він повинен бути нетранзакційним, оскільки Spring Framework (а не контейнер Java EE) управляє транзакціями.

У цьому випадку бін txManager має тип HibernateTransactionManager. Так само, як DataSourceTransactionManager необхідне посилання на DataSource, HibernateTransactionManager необхідне посилання на SessionFactory. У наступному прикладі оголошено біни sessionFactory та txManager:


<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
        </value>
    </property>
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

Якщо ти використовуєш Hibernate і керовані контейнером Java EE транзакції JTA, слід використовувати той же JtaTransactionManager, що і в попередньому прикладі з JTA для JDBC, як це показано в наступному прикладі. Також рекомендується зробити так, щоб Hibernate дізнавався про JTA через свій координатор транзакцій і, можливо, конфігурацію режиму звільнення з'єднання:


<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
            hibernate.transaction.coordinator_class=jta
            hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
        </value>
    </property>
</bean>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

Або можна передати JtaTransactionManager до твого LocalSessionFactoryBean для забезпечення тих же значень за замовчуванням:


<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
        </value>
    </property>
    <property name="jtaTransactionManager" ref="txManager"/>
</bean>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>