8. Обеспечить выполнение метода в новой транзакции через Spring-прокси
Условие задачи:
Дан сервис с двумя транзакционными методами. Метод methodB() должен выполняться в отдельной транзакции с Propagation.REQUIRES_NEW.
При прямом вызове methodB() из methodA() вызов не проходит через Spring-прокси, поэтому новая транзакция не создаётся.
Код:
@Service
public class TransactionService {
@Transactional
public void methodA() {
methodB();
}
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
public void methodB() {
// some logic
}
}
Спойлеры к решению
Подсказки
@Transactional в стандартном proxy-режиме применяется при вызове через Spring-прокси.💡 Вызов
this.methodB() является self-invocation и обходит прокси.💡 Можно получить прокси текущего объекта через
AopContext.💡 Другой вариант — внедрить прокси самого сервиса через
@Lazy.💡 Для
AopContext.currentProxy() необходимо включить exposeProxy = true.Решение
Вариант через AopContext:
@Service
public class TransactionService {
@Transactional
public void methodA() {
TransactionService proxy =
(TransactionService) AopContext.currentProxy();
proxy.methodB();
}
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
public void methodB() {
// выполнение в новой транзакции
}
}
Для доступа к текущему прокси необходимо включить его публикацию:
@Configuration
@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {
}
Теперь вызов:
proxy.methodB();
проходит через Spring AOP, поэтому REQUIRES_NEW срабатывает.
Транзакция methodA() временно приостанавливается, создаётся новая транзакция для methodB(), а после её завершения продолжается первая транзакция.
Альтернативный вариант — self-injection:
@Service
public class TransactionService {
private final TransactionService self;
public TransactionService(
@Lazy TransactionService self
) {
this.self = self;
}
@Transactional
public void methodA() {
self.methodB();
}
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
public void methodB() {
// выполнение в новой транзакции
}
}
Здесь self содержит ссылку на Spring-прокси, поэтому вызов methodB() также перехватывается транзакционным interceptor.
На практике ещё более простым вариантом часто является вынесение methodB() в отдельный Spring-сервис и вызов его через внедрённый бин.