14. Разобраться с транзакциями при self-invocation
Условие задачи:
Дан сервис с двумя транзакционными методами:
@Service
class SomeService {
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
public void a() {
}
@Transactional
public void b() {
a();
}
}
Метод b() вызывается через Spring-бин:
someService.b();
Необходимо ответить:
Сколько транзакций будет создано?
Почему
REQUIRES_NEWна методеa()не срабатывает?Как сделать так, чтобы метод
a()выполнялся в новой транзакции?
Спойлеры к решению
Подсказки
@Transactional обычно работает через Spring AOP-прокси.💡 Внешний вызов метода бина проходит через прокси.
💡 Внутренний вызов метода того же объекта называется self-invocation.
💡 Вызов
a() внутри b() фактически выполняется как this.a().💡 Надёжнее всего вынести
a() в отдельный Spring-бин.Решение
При вызове b() будет создана одна транзакция.
Внешний вызов b() проходит через Spring-прокси, поэтому аннотация @Transactional срабатывает и открывает транзакцию.
Вызов a() происходит внутри того же объекта:
public void b() {
this.a();
}
Такой вызов не проходит через прокси. Поэтому настройки Propagation.REQUIRES_NEW не применяются, а метод a() выполняется внутри транзакции метода b().
Предпочтительный вариант — вынести метод a() в отдельный сервис:
@Service
public class AService {
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
public void a() {
// код
}
}
@Service
public class SomeService {
private final AService aService;
public SomeService(AService aService) {
this.aService = aService;
}
@Transactional
public void b() {
aService.a();
}
}
Теперь вызов aService.a() проходит через прокси другого Spring-бина.
В результате:
для
b()создаётся первая транзакция;перед выполнением
a()первая транзакция приостанавливается;для
a()создаётся новая независимая транзакция;после завершения
a()продолжается транзакция методаb().
Альтернативный вариант — вызвать метод через прокси текущего сервиса:
@Service
public class SomeService {
private final SomeService self;
public SomeService(
@Lazy SomeService self
) {
this.self = self;
}
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
public void a() {
// код
}
@Transactional
public void b() {
self.a();
}
}
Такой подход работает, но усложняет структуру класса. Обычно отдельный сервис является более понятным решением.