В Spring, распространяется ли аннотация @Transactional с метода A на метод B, если B вызывается внутри A через другой бина?

Ответ

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

Пример:

@Service
class ServiceA {
    @Transactional
    public void methodA() {
        // Этот вызов НЕ будет частью транзакции methodA
        serviceB.methodB();
    }
}

@Service
class ServiceB {
    // Без собственной @Transactional выполнится auto-commit
    public void methodB() {
        // Операции с БД
    }
}

Почему так происходит? Spring внедряет транзакционность через прокси, который перехватывает вызовы извне. Внутренние вызовы между бинами не перехватываются.

Решение:

  1. Добавить @Transactional на метод B.
  2. Использовать самовызов в одном классе (только если оба метода в одном бине):
    @Service
    class ServiceA {
        @Transactional
        public void methodA() {
            this.methodB(); // Будет в одной транзакции (режим прокси)
        }
        public void methodB() { /* ... */ }
    }

    Примечание: для самовызовов может потребоваться режим AspectJ вместо стандартного прокси.

Ответ 18+ 🔞

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

Spring AOP, этот хитрожопый ублюдок, он транзакции через прокси организует. Прокси, Карл! Это как будто ты не к реальному объекту обращаешься, а к его двойнику-охраннику, который стоит на входе и говорит: «Стой, блядь! Сейчас я тут транзакцию открою, а потом закрою».

И вот представь: ты в методе methodA(), который уже в транзакции, и оттуда зовешь serviceB.methodB(). Так вот, этот вызов — он же внутренний, между бинами! Прокси ServiceA уже отработал на входе в methodA(), а тут ты внутри него прыг-скок — и напрямую в ServiceB. А прокси ServiceB тебя не ждал изнутри, он ждёт вызовы извне. И получается, что methodB() выполняется вообще без транзакции, на автокоммите, как голый в мороз. Пиздец, да?

@Service
class ServiceA {
    @Transactional
    public void methodA() {
        // А вот этот вызов, сука, прокси не перехватит!
        // Он просто пролетит мимо кассы, как воришка в супермаркете.
        serviceB.methodB(); // Транзакции тут — ноль ебать!
    }
}

@Service
class ServiceB {
    // А тут @Transactional нет, значит, каждая операция — отдельный коммит.
    public void methodB() {
        // Делаем что-то с БД — и всё на автокоммите, ёпта!
    }
}

И что делать-то, спрашивается? Варианта два, как выкрутиться из этой ебалы.

  1. Самый простой и правильный, блядь. Просто на methodB() тоже повесь @Transactional. Тогда когда ServiceA вызовет ServiceB, прокси второго уже вступит в игру и заведёт свою, новую транзакцию (или присоединится к существующей, смотря как настроишь). Красота!

  2. А вот если оба метода в одном классе, тут вообще цирк. Самовызов, сука!

    @Service
    class ServiceA {
        @Transactional
        public void methodA() {
            this.methodB(); // А вот так — будет в одной транзакции!
        }
        public void methodB() { /* ... */ }
    }

    Но и тут подстава! При стандартном проксировании Spring AOP (на интерфейсах или CGLIB) вызов this.methodB() — это же прямой вызов, минуя прокси! Так что транзакции на methodB() тоже не будет. Чтобы это работало, надо либо переходить на полный режим AspectJ (это уже овердохуища возни), либо выносить метод в отдельный бин. Короче, головняк ещё тот.

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