В Spring: создастся ли новая транзакция, если метод с @Transactional вызывает другой @Transactional метод внутри того же сервиса?

Ответ

Нет, новая транзакция не создастся по умолчанию. Внутренний вызов будет выполняться в рамках существующей транзакции внешнего метода.

Почему? Это связано с работой Spring AOP (проксирование). Когда вызов происходит внутри одного класса, он минует прокси-обертку, и механизм управления транзакциями не перехватывается. Такое поведение называется self-invocation.

Пример:

@Service
public class TransactionalService {

    @Transactional
    public void outerMethod() {
        // Начинается транзакция Tx1
        innerMethod(); // Этот вызов НЕ приведет к созданию новой транзакции.
        // Вся работа выполняется в Tx1
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void innerMethod() {
        // Несмотря на аннотацию, этот код выполнится в контексте Tx1,
        // а НЕ в новой независимой транзакции.
    }
}

Как заставить это работать?

  1. Вынести innerMethod() в отдельный бин и инжектить его.
  2. Получить ссылку на собственный прокси через AopContext.currentProxy() (требует активации exposeProxy).
  3. Использовать программируемое управление транзакциями (TransactionTemplate).

Ответ 18+ 🔞

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

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

Почему? А потому что вся эта магия с транзакциями — это прокси-обёртки, которые Spring натягивает на твой бин. И когда ты извне вызываешь outerMethod(), то попадаешь как раз в эту обёртку. Она говорит: «О, транзакция!» — и заворачивает вызов в неё.

А вот когда внутри outerMethod() ты просто так, по-пацански, вызываешь innerMethod() — ты же вызываешь его напрямую, минуя всю эту прокси-хуярси! Ты как будто обходишь турникет в метро, блядь. Spring про этот вызов ничего не знает, и его аннотации — просто красивые бумажки. Вся работа идёт в той же самой, первой транзакции. Это называется self-invocation, или, по-нашему, «самоёбство».

Вот смотри, как это выглядит в коде, сука:

@Service
public class TransactionalService {

    @Transactional
    public void outerMethod() {
        // Тут стартует транзакция Tx1, всё чинно-благородно.
        innerMethod(); // А вот этот вызов — пиздец обман! Новой транзакции НЕ БУДЕТ!
        // Всё варится в одной кастрюле Tx1.
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW) // Смотри какая аннотация пафосная! НОВАЯ! А по факту — хуй соси.
    public void innerMethod() {
        // И этот код, вместо того чтобы работать в своей песочнице, будет тащиться в старой транзакции Tx1.
        // Аннотация проигнорирована нахуй.
    }
}

Вот так-то, блядь. Ожидание — отдельная независимая транзакция, реальность — всё в одной куче.

Ну и как с этим бороться, ебать? Варианты есть:

  1. Самый нормальный. Вынести этот внутренний метод в отдельный бин. И инжектить его себе. Тогда Spring будет управлять вызовами к нему через прокси, и транзакции заработают как надо. Чистая архитектура, мать её.
  2. Костыль, но работает. Заставить Spring показывать тебе твой же собственный прокси через AopContext.currentProxy(). Но для этого надо включить какую-то настройку (exposeProxy), и выглядит это всё как подпольная сделка, честно говоря.
  3. Для мазохистов. Забить на декларативные аннотации и управлять транзакциями вручную, через TransactionTemplate. Сам всё запускаешь, сам завершаешь. Полный контроль, но и возни — овердохуища.

Короче, запомни, чувак: транзакции в Spring — это магия, но магия работает только через прокси. А если ты внутри класса сам себя вызываешь — это не магия, а самообман, блядь.