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 // 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 // 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)) }
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ