Ключом к пониманию абстракции транзакций 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" />
<!-- другие определения <bean/--> -->
</beans>
JtaTransactionManager
не нужно знать об DataSource
(или любых других специфических ресурсах), поскольку он использует глобальную инфраструктуру управления транзакциями контейнера.
dataSource
используется тег <jndi-lookup/>
из пространства имен jee
. Для получения дополнительной информации см. раздел "Схема JEE".При любом наборе заданных значений для транзакций 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"/>
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ