Spring Framework предлагает два способа программного управления транзакциями – с помощью:
-
TransactionTemplate
илиTransactionalOperator
. -
Непосредственно реализации
TransactionManager
.
Команда Spring обычно рекомендует TransactionTemplate
для программного управления транзакциями в императивных потоках и TransactionalOperator
для реактивного кода. Второй подход похож на использование API-интерфейса UserTransaction
из JTA, хотя обработка исключений менее перегруженная.
Использование TransactionTemplate
Шаблон TransactionTemplate
задействует тот же подход, что и другие шаблоны Spring, такие как JdbcTemplate
. Он задействует подход обратного вызова (чтобы освободить код приложения от необходимости выполнять стереотипное получение и освобождение транзакционных ресурсов) и влечет за собой создание кода, который управляется намерением, т.е. ваш работа вашего кода сосредоточена исключительно на том, что вам нужно осуществить.
TransactionTemplate
полностью привязывает вас к инфраструктуре транзакций и API-интерфейсу Spring. Подходит ли программное управление транзакциями для ваших потребностей разработки – это вы должны решать самостоятельно.Код приложения, который должен выполняться в транзакционном контексте и который явным образом использует TransactionTemplate
, похож на приведенный далее пример. Вы, как разработчик приложения, можете написать реализацию TransactionCallback
(обычно выраженную как анонимный внутренний класс), содержащую код, который нужно выполнить в контексте транзакции. Затем вы можете передать экземпляр вашего кастомного TransactionCallback
в метод execute(..)
, открытый для TransactionTemplate
. В следующем примере показано, как это сделать:
public class SimpleService implements Service {
// единый TransactionTemplate, общий для всех методов данного экземпляра
private final TransactionTemplate transactionTemplate;
// используем внедрение зависимостей через конструктор для предоставления PlatformTransactionManager
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public Object someServiceMethod() {
return transactionTemplate.execute(new TransactionCallback() {
// код в этом методе выполняется в контексте транзакции
public Object doInTransaction(TransactionStatus status) {
updateOperation1();
return resultOfUpdateOperation2();
}
});
}
}
// используем внедрение зависимостей через конструктор для предоставления PlatformTransactionManager
class SimpleService(transactionManager: PlatformTransactionManager) : Service {
// единый TransactionTemplate, общий для всех методов данного экземпляра
private val transactionTemplate = TransactionTemplate(transactionManager)
fun someServiceMethod() = transactionTemplate.execute<Any?> {
updateOperation1()
resultOfUpdateOperation2()
}
}
Если возвращаемого значения нет, можно использовать вспомогательный класс TransactionCallbackWithoutResult
с анонимным классом, как это показано ниже:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
override fun doInTransactionWithoutResult(status: TransactionStatus) {
updateOperation1()
updateOperation2()
}
})
Код внутри обратного вызова может откатить транзакцию назад, вызвав метод setRollbackOnly()
для предоставленного объекта TransactionStatus
, как показано ниже:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessException ex) {
status.setRollbackOnly();
}
}
});
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
override fun doInTransactionWithoutResult(status: TransactionStatus) {
try {
updateOperation1()
updateOperation2()
} catch (ex: SomeBusinessException) {
status.setRollbackOnly()
}
}
})
Задание параметров транзакции
Параметры транзакции (такие как режим распространения, уровень изоляции, время ожидания и так далее) можно задать для шаблоне TransactionTemplate
программно или в конфигурации. По умолчанию экземпляры TransactionTemplate
имеют стандартные настройки транзакций. В следующем примере показана программная настройка параметров транзакций для конкретного TransactionTemplate
:
public class SimpleService implements Service {
private final TransactionTemplate transactionTemplate;
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
// при желании здесь можно явным образом задать параметры транзакции
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
this.transactionTemplate.setTimeout(30); // 30 секунд
// и так далее...
}
}
class SimpleService(transactionManager: PlatformTransactionManager) : Service {
private val transactionTemplate = TransactionTemplate(transactionManager).apply {
// при желании здесь можно явным образом задать параметры транзакции
isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
timeout = 30 // 30 секунд
// и так далее...
}
}
В следующем примере TransactionTemplate
определен с некоторыми кастомными настройками транзакций с помощью XML-конфигурации в Spring:
<bean id="sharedTransactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="timeout" value="30"/>
</bean>
Затем можно внедрить sharedTransactionTemplate
в такое количество служб, какое потребуется.
Наконец, экземпляры класса TransactionTemplate
являются потокобезопасными, поскольку экземпляры не сохраняют никакого диалогового состояния. Однако экземпляры TransactionTemplate
сохраняют конфигурационное состояние. Таким образом, хотя несколько классов могут совместно использовать один экземпляр TransactionTemplate
, если нужно, чтобы класс использовал TransactionTemplate
с различными параметрами (например, с различным уровнем изоляции), необходимо создать два разных экземпляра TransactionTemplate
.
Использование TransactionalOperator
TransactionalOperator
следует программной структуре оператора, который схож с другими реактивными операторами. Он задействует подход обратного вызова (чтобы освободить код приложения от необходимости выполнять стереотипное получение и освобождение транзакционных ресурсов) и влечет за собой создание кода, который управляется намерением, т.е. ваш работа вашего кода сосредоточена исключительно на том, что вам нужно осуществить.
TransactionalOperator
полностью привязывает вас к инфраструктуре транзакций и API-интерфейсу Spring. Подходит ли программное управление транзакциями для ваших потребностей разработки – это вы должны решать самостоятельно.Код приложения, который нужно выполнить в контексте транзакции и который явным образом задействует TransactionalOperator
, похож на тот, что представлен в следующем примере:
public class SimpleService implements Service {
// единый TransactionalOperator, общий для всех методов данного экземпляра
private final TransactionalOperator transactionalOperator;
// используем внедрение зависимостей через конструктор для предоставления ReactiveTransactionManager
public SimpleService(ReactiveTransactionManager transactionManager) {
this.transactionalOperator = TransactionalOperator.create(transactionManager);
}
public Mono<Object> someServiceMethod() {
// код в этом методе выполняется в контексте транзакции
Mono<Object> update = updateOperation1();
return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
}
}
// используем внедрение зависимостей через конструктор для предоставления ReactiveTransactionManager
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {
// единый TransactionalOperator, общий для всех методов данного экземпляра
private val transactionalOperator = TransactionalOperator.create(transactionManager)
suspend fun someServiceMethod() = transactionalOperator.executeAndAwait<Any?> {
updateOperation1()
resultOfUpdateOperation2()
}
}
TransactionalOperator
можно использовать двумя способами:
-
На основе оператора, использующего типы из Project Reactor
(mono.as(transactionalOperator::transactional)
) -
На основе обратного вызова для всех остальных случаев (
transactionalOperator.execute(TransactionCallback<T>)
)
Код внутри обратного вызова может откатить транзакцию назад, вызвав метод setRollbackOnly()
для предоставленного объекта ReactiveTransaction
, как показано ниже:
transactionalOperator.execute(new TransactionCallback<>() {
public Mono<Object> doInTransaction(ReactiveTransaction status) {
return updateOperation1().then(updateOperation2)
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
}
}
});
transactionalOperator.execute(object : TransactionCallback() {
override fun doInTransactionWithoutResult(status: ReactiveTransaction) {
updateOperation1().then(updateOperation2)
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly())
}
})
Сигналы отмены
В Reactive Streams Subscriber
может отменить свою Subscription
и остановить своего Publisher
. Операторы в Project Reactor, а также в других библиотеках, такие как next()
, take(long)
, timeout(Duration)
и другие, могут осуществлять отмены. Узнать причину отмены, будь то ошибка или просто отсутствие интереса к дальнейшему потреблению, невозможно. Начиная с версии 5.3 сигналы отмены приводят к откату. В результате важно рассмотреть операторов, используемых в нисходящем от Publisher
транзакций порядке. В частности, в случае Flux
или другого многозначного Publisher
, для завершения транзакции должен быть потреблен полный вывод.
Задание параметров транзакции
Вы можете задавать параметры транзакции (такие как режим распространения, уровень изоляции, время ожидания и т.д.) для TransactionalOperator
. По умолчанию экземпляры TransactionalOperator
имеют стандартные транзакционные параметры. В следующем примере показана настройка транзакционных параметров для конкретного TransactionalOperator
:
public class SimpleService implements Service {
private final TransactionalOperator transactionalOperator;
public SimpleService(ReactiveTransactionManager transactionManager) {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// при желании здесь можно явным образом задать параметры транзакции
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
definition.setTimeout(30); // 30 секунд
// и так далее...
this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
}
}
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {
private val definition = DefaultTransactionDefinition().apply {
// при желании здесь можно явным образом задать параметры транзакции
isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
timeout = 30 // 30 секунд
// и так далее...
}
private val transactionalOperator = TransactionalOperator(transactionManager, definition)
}
Использование TransactionManager
В следующих разделах рассказывается о программном использовании императивных и реактивных диспетчеров транзакций.
Использование PlatformTransactionManager
Для императивных транзакций можно использовать org.springframework.transaction.PlatformTransactionManager
непосредственно для управления транзакцией. Для этого передайте реализацию используемого вами PlatformTransactionManager
в ваш бин через ссылку на бин. Затем, используя объекты TransactionDefinition
и TransactionStatus
, можно инициировать транзакции, откатывать и фиксировать. В следующем примере показано, как это сделать:
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// явную установку имени транзакции можно сделать только программно
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// помещаем сюда вашу бизнес-логику
} catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
val def = DefaultTransactionDefinition()
// явную установку имени транзакции можно сделать только программно
def.setName("SomeTxName")
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
val status = txManager.getTransaction(def)
try {
// помещаем сюда вашу бизнес-логику
} catch (ex: MyException) {
txManager.rollback(status)
throw ex
}
txManager.commit(status)
Использование ReactiveTransactionManager
При работе с реактивными транзакциями можно использовать org.springframework.transaction.ReactiveTransactionManager
непосредственно для управления транзакцией. Для этого передайте реализацию используемого вами ReactiveTransactionManager
в ваш бин через ссылку на бин. Затем, используя объекты TransactionDefinition
и ReactiveTransaction
, можно инициировать транзакции, откатывать и фиксировать. В следующем примере показано, как это сделать:
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// явную установку имени транзакции можно сделать только программно
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def);
reactiveTx.flatMap(status -> {
Mono<Object> tx = ...; // помещаем сюда вашу бизнес-логику
return tx.then(txManager.commit(status))
.onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
});
val def = DefaultTransactionDefinition()
// явную установку имени транзакции можно сделать только программно
def.setName("SomeTxName")
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
val reactiveTx = txManager.getReactiveTransaction(def)
reactiveTx.flatMap { status ->
val tx = ... // помещаем сюда вашу бизнес-логику
tx.then(txManager.commit(status))
.onErrorResume { ex -> txManager.rollback(status).then(Mono.error(ex)) }
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ