Обеспечить создание транзакции при внутреннем вызове через self-inject

10. Обеспечить создание транзакции при внутреннем вызове через self-inject

Условие задачи:
Дан Spring-сервис с двумя методами: a() и b().

Метод a() помечен @Transactional, а из метода b() выполняется внутренний вызов a().

Необходимо определить, сколько транзакций будет создано при внешнем вызове b(), и исправить код с помощью self-injection так, чтобы a() вызывался через Spring-прокси.

Код:

@Service
class SomeService {

    @Transactional
    public void a() {
    }

    public void b() {
        a();
    }
}

Вызов:

someService.b();

Спойлеры к решению

Подсказки
💡 Метод b() не помечен @Transactional.
💡 Вызов a() внутри того же объекта является self-invocation.
💡 Такой вызов не проходит через Spring-прокси.
💡 Нужно получить прокси текущего сервиса и вызвать a() через него.
💡 При self-injection удобно использовать @Lazy, чтобы избежать циклической зависимости при создании бина.

Решение

При обычном вызове:

someService.b();

будет создано 0 транзакций.

Метод b() сам не является транзакционным, а вызов:

a();

фактически выполняется как:

this.a();

Он не проходит через Spring-прокси, поэтому @Transactional на a() не срабатывает.

Вариант с self-injection:

@Service
public class SomeService {

    private final SomeService self;

    public SomeService(
            @Lazy SomeService self
    ) {
        this.self = self;
    }

    @Transactional
    public void a() {
        // работа в транзакции
    }

    public void b() {
        self.a();
    }
}

Теперь вызов:

self.a();

проходит через Spring-прокси. Аннотация @Transactional перехватывается транзакционным interceptor, и для метода a() создаётся транзакция.

В результате при вызове:

someService.b();

будет создана 1 транзакция — для метода a().