← Назад к вопросам

Распространяется ли влияние аннотации @Transactional, расположенной над методом A, на метод B, если метод B вызывается внутри метода A через объект другого класса

3.0 Senior🔥 91 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

@Transactional на методе A и вызов метода B из другого класса

Ответ: ДА, распространяется, но только если метод B тоже помечен @Transactional. Это связано с механизмом Spring AOP (Aspect-Oriented Programming) и propagation уровнями транзакций.

Механизм работы @Transactional

Spring использует dynamic proxy (во время выполнения) для перехвата вызовов методов с аннотацией @Transactional:

// Исходный код
@Component
public class UserService {
    @Transactional
    public void methodA() {
        // Код
    }
}

// Spring создаёт PROXY
public class UserService_Proxy extends UserService {
    private TransactionManager tm;
    
    @Override
    public void methodA() {
        Transaction tx = tm.begin();  // START TRANSACTION
        try {
            super.methodA();           // Вызов оригинального метода
            tm.commit();               // COMMIT
        } catch (Exception e) {
            tm.rollback();             // ROLLBACK
            throw e;
        }
    }
}

Сценарий 1: Метод B без @Transactional

@Component
public class UserService {
    @Autowired
    private OrderService orderService;
    
    @Transactional
    public void methodA() {
        // Создаём пользователя (в транзакции)
        createUser();
        
        // Вызываем orderService.methodB()
        orderService.methodB();  // Без @Transactional
    }
    
    private void createUser() {
        // Сохраняем в БД
    }
}

@Component
public class OrderService {
    public void methodB() {  // НЕ @Transactional
        // Создаём заказ (БЕЗ транзакции)
        createOrder();
    }
    
    private void createOrder() {
        // Сохраняем в БД
    }
}

Что происходит:

methodA() {
  ┌─────────────────────────────────┐
  │ TRANSACTION (STARTED by @Transactional)
  │                                 │
  │  createUser()  ✓ (в транзакции)│
  │                                 │
  │  orderService.methodB() ──────┐ │
  │                              │ │
  └──────────────────────────────┼─┘
                                │ ВЫЗОВ orderService.methodB()
                                │ Через PROXY orderService
                                │ БЕЗ начала новой транзакции!
                                │
                                ↓
methodB() {  // НЕ @Transactional
  createOrder()  ✓ (в СУЩЕСТВУЮЩЕЙ транзакции!)
              ↑
        Использует уже открытую
        транзакцию из methodA
}

Результат: orderService.methodB() использует существующую транзакцию из методе А!

// Если methodA() откатится, откатятся И createUser, И createOrder
// Они в ОДНОЙ транзакции

Сценарий 2: Метод B с @Transactional (PROPAGATION.REQUIRED — по умолчанию)

@Component
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED)  // По умолчанию
    public void methodB() {  // С @Transactional
        createOrder();
    }
}

Что происходит:

Пропагация: REQUIRED
"Если уже есть транзакция, используй её. Если нет, создай новую"

methodA() {                    ← Начало: нет транзакции
  TRANSACTION-1 START
  createUser()  (в TRANSACTION-1)
  orderService.methodB()  {
    // Проверка: есть ли открытая транзакция?
    // ДА! TRANSACTION-1
    // Используем TRANSACTION-1, не создаём новую
    createOrder()  (в TRANSACTION-1)
  }
  TRANSACTION-1 COMMIT/ROLLBACK
}

Результат: обе операции в ОДНОЙ транзакции.

Сценарий 3: Метод B с PROPAGATION.REQUIRES_NEW

@Component
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {  // Новая транзакция!
        createOrder();
    }
}

Что происходит:

methodA() {
  TRANSACTION-1 START (для methodA)
  createUser()  (в TRANSACTION-1)
  
  orderService.methodB() {  // REQUIRES_NEW
    // Прерываем TRANSACTION-1 (suspend)
    // Создаём НОВУЮ: TRANSACTION-2
    createOrder()  (в TRANSACTION-2)
    // TRANSACTION-2 COMMIT
    // Возобновляем TRANSACTION-1 (resume)
  }
  
  TRANSACTION-1 COMMIT/ROLLBACK
}

Критичный результат:

  • methodB() коммитится сразу (в TRANSACTION-2)
  • Даже если methodA() откатится, orderService.methodB() останется в БД!
// Пример проблемы
@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
    from.withdraw(amount);  // TRANSACTION-1
    
    // Если здесь ошибка, TRANSACTION-1 откатится
    // Но если notifyService.sendNotification() использует
    // PROPAGATION.REQUIRES_NEW, оно уже закоммитилось!
    
    notifyService.sendNotification(...);
    
    throw new Exception("Ошибка!");
}

Таблица Propagation уровней

PropagationПоведениеПример
REQUIRED (default)Если транзакция есть, используй. Если нет, создай@Transactional обычно
REQUIRES_NEWВСЕГДА создавай новую транзакциюЛогирование, мониторинг
SUPPORTSЕсли есть транзакция, используй. Если нет, работай без неёЧтение данных
NOT_SUPPORTEDРаботай БЕЗ транзакции, приостанови текущуюОперации, не требующие ACID
MANDATORYТранзакция ДОЛЖНА быть. Иначе ошибкаКритичные операции
NEVERТранзакция НЕ должна быть. Иначе ошибкаСпециальные случаи
NESTEDВложенная транзакция (savepoint)Сложные операции

Практический пример

@Component
public class PaymentService {
    @Autowired
    private AccountService accountService;
    @Autowired
    private NotificationService notificationService;
    
    @Transactional
    public void processPayment(Payment payment) {
        // TRANSACTION: PAYMENT
        
        // Требует транзакции (REQUIRED)
        accountService.withdraw(payment.getFromId(), payment.getAmount());
        accountService.deposit(payment.getToId(), payment.getAmount());
        
        // НЕ требует транзакции (отправить письмо)
        notificationService.sendEmail(payment.getRecipient());
        // ↑ Это ТРЕБУЕТ REQUIRES_NEW, иначе если письмо упадёт,
        //   откатится вся платёж!
    }
}

@Component
public class AccountService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void withdraw(Long id, BigDecimal amount) {
        // Использует TRANSACTION: PAYMENT
        Account account = repo.findById(id);
        account.setBalance(account.getBalance().subtract(amount));
        repo.save(account);
    }
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void deposit(Long id, BigDecimal amount) {
        // Использует TRANSACTION: PAYMENT
        Account account = repo.findById(id);
        account.setBalance(account.getBalance().add(amount));
        repo.save(account);
    }
}

@Component
public class NotificationService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendEmail(String email) {
        // TRANSACTION: NOTIFICATION (отдельная)
        // Если она упадёт, processPayment НЕ откатится
        emailService.send(email);
    }
}

Критичный момент: Self-invocation

⚠️ ВАЖНО! Если метод B в ТОМ ЖЕ классе, что A, то proxy не работает:

@Component
public class UserService {
    @Transactional
    public void methodA() {
        methodB();  // ПРЯМОЙ вызов, не через proxy!
        // @Transactional на methodB НЕ сработает!
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // Это НЕ создаст новую транзакцию
        // Потому что вызов напрямую, без proxy
    }
}

Решение: injectedь себя через ObjectProvider

@Component
public class UserService {
    @Autowired
    private ObjectProvider<UserService> selfProxy;
    
    @Transactional
    public void methodA() {
        // Вызов через proxy
        selfProxy.getIfAvailable().methodB();
        // Теперь @Transactional на methodB сработает!
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // Создаст новую транзакцию ✓
    }
}

Ответ на вопрос

Распространяется ли влияние @Transactional от методе A на метод B (другой класс)?

ДА, но:

  1. Если methodB НЕ имеет @Transactional:

    • Использует существующую транзакцию из methodA
    • Всё откатится вместе при ошибке
  2. Если methodB имеет @Transactional с REQUIRED:

    • Использует существующую транзакцию из methodA
    • Всё откатится вместе при ошибке
  3. Если methodB имеет @Transactional с REQUIRES_NEW:

    • Создаёт НОВУЮ транзакцию
    • Коммитится независимо от methodA
    • Откат methodA не откатит methodB
  4. Если методы в ОДНОМ классе:

    • @Transactional НЕ работает из-за прямого вызова, без proxy
    • Нужно использовать ObjectProvider для вызова через proxy

Практический совет: явно указывай propagation уровень для ясности:

@Transactional(propagation = Propagation.REQUIRED)  // явно
public void methodB() { }