Обеспечить выполнение метода в новой транзакции через Spring-прокси

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-сервис и вызов его через внедрённый бин.