Начнется ли транзакция, если нетранзакционный метод вызывает транзакционный метод внутри одного Spring-бина?

Ответ

Нет, транзакция не начнется. Это классическое ограничение работы Spring AOP-прокси.

Причина: Транзакционность в Spring реализуется через прокси-объекты. Внутренний вызов метода (this.transactionalMethod()) минует прокси, и, следовательно, перехватчик транзакций не срабатывает.

Пример проблемы:

@Service
public class UserService {

    public void createUserNonTransactional(String name) {
        // Этот вызов происходит напрямую, минуя прокси.
        createUserTransactional(name); // Транзакция НЕ БУДЕТ создана!
    }

    @Transactional
    public void createUserTransactional(String name) {
        // Логика, требующая транзакции
        userRepository.save(new User(name));
    }
}

Способы решения:

  1. Self-injection: Внедрить самого себя и вызывать метод через прокси.

    @Service
    public class UserService {
        @Autowired
        private UserService self; // Ссылка на прокси
    
        public void createUserNonTransactional(String name) {
            self.createUserTransactional(name); // Теперь транзакция работает.
        }
        // ...
    }
  2. Разделение на разные бины: Вынести транзакционный метод в отдельный сервисный компонент.
  3. Использование AspectJ mode: Настройка Spring на использование AspectJ для компоновки во время загрузки (LTW) или компиляции (CTW) обходит это ограничение, но сложнее в настройке.

Ответ 18+ 🔞

А, ну это же классика, блядь! Вечный геморрой с этими проксями в Spring'е. Сидишь такой, пишешь код, думаешь: «Ну всё, сейчас я тут транзакцию красиво заверну, и будет нам счастье». А в итоге нихуя не работает, и ты полдня ломаешь голову, как будто в первый раз.

Вот смотри, в чём тут собака зарыта, а точнее — сука, прокси минует. Spring, он же хитрая жопа, чтобы транзакции подсунуть, оборачивает твой бин в прокси-обёртку. И когда ты извне вызываешь метод, всё ок — попадаешь в прокси, он там транзакцию стартует, потом в реальный метод проваливается.

Но! Если ты внутри одного и того же бина из одного метода вызываешь другой — ты ж вызываешь его напрямую, через this. А this — это же не прокси, ёпта! Это просто объект, голый и неприкрытый. Прокси остаётся в сторонке, грустно курит, и транзакция, соответственно, не начинается. Вообще.

Вот тебе наглядный пиздец:

@Service
public class UserService {

    public void createUserNonTransactional(String name) {
        // А вот тут облом, чувак. Вызов идёт мимо всех этих spring'овых приблуд.
        createUserTransactional(name); // Транзакция? НИ ХУЯ!
    }

    @Transactional
    public void createUserTransactional(String name) {
        // Тут по логике должна быть транзакция, но её нет, как нет и твоих надежд.
        userRepository.save(new User(name));
    }
}

И что делать-то, спросишь? Варианты есть, не печалься.

  1. Самовнедрение (Self-injection). Звучит как извращение, но работает. Просишь Spring: «Дай-ка мне ссылку не на самого себя, а на свою же прокси-обёртку».

    @Service
    public class UserService {
        @Autowired
        private UserService self; // Это не я, это моя официальная представительница — прокся!
    
        public void createUserNonTransactional(String name) {
            self.createUserTransactional(name); // А вот теперь — да, транзакция, мать её, работает!
        }
        // ...
    }

    Немного пахнет псиной, но жить можно.

  2. Разделяй и властвуй. Выносишь весь транзакционный функционал в отдельный сервисный бин. И вызываешь уже его. Чистая архитектура, никакой магии.

  3. AspectJ mode. Это уже для мазохистов и гиков. Настраиваешь Spring на использование полной мощи AspectJ, который будет впиливать транзакционность прямо в байт-код, минуя прокси. Работает везде, но настройка — тот ещё квест. Овердохуища мороки.

Короче, запомни раз и навсегда: внутренние вызовы в Spring AOP не работают. Это не баг, это такая пиздатая особенность. Теперь ты в курсе, и не будешь, как я когда-то, биться головой об клавиатуру, крича «Почему не начинается, блядь?!». Всё из-за этой прокси, сука.