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

Что происходит при возникновении исключения в транзакции

1.7 Middle🔥 131 комментариев
#Spring Framework

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

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

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

Что происходит при возникновении исключения в транзакции

При возникновении исключения в транзакции происходит откат всех выполненных операций - это обеспечивает целостность данных.

Поведение по умолчанию

@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
    Account from = accountRepository.findById(fromId).get();
    Account to = accountRepository.findById(toId).get();
    
    from.setBalance(from.getBalance().subtract(amount));
    to.setBalance(to.getBalance().add(amount));
    
    // Если тут выброситься исключение - откатит ВСЮ транзакцию
    if (to.getBalance().compareTo(BigDecimal.ZERO) < 0) {
        throw new InsufficientFundsException();
    }
}

Если выброситься любое RuntimeException:

  • Все изменения откатываются (ROLLBACK)
  • Ни один UPDATE не будет применён
  • Состояние БД как будто операция не начиналась

Checked vs Unchecked исключения

@Transactional
public void process() throws IOException {
    // Checked exception - НЕ откатывает по умолчанию
    throw new IOException("Ошибка файла");
}

@Transactional
public void process() {
    // Unchecked exception - откатывает всегда
    throw new RuntimeException("Ошибка");
}

Это поведение можно переопределить:

@Transactional(rollbackFor = {IOException.class, MyException.class})
public void process() throws IOException {
    // Теперь IOException тоже откатит транзакцию
    throw new IOException();
}

@Transactional(noRollbackFor = {RuntimeException.class})
public void process() {
    // RuntimeException НЕ откатит транзакцию
    throw new RuntimeException();
}

Откат с try-catch

@Transactional
public void process() {
    try {
        // Операции с БД
        userRepository.save(user);
    } catch (Exception e) {
        // Если ловишь исключение - откат НЕ происходит!
        System.out.println("Ошибка обработана");
    }
    
    // Код продолжает выполняться, транзакция коммитится
}

Решение - пробросить исключение дальше или явно откати:

@Transactional
public void process() {
    try {
        userRepository.save(user);
    } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        // или просто пробросить:
        throw new RuntimeException("Ошибка обработки", e);
    }
}

Частичный откат с вложенными транзакциями

@Transactional
public void parentTransaction() {
    userRepository.save(user1);
    
    try {
        childTransaction();
    } catch (Exception e) {
        // Вложенная транзакция откатилась, но родительская продолжает
        System.out.println("Обработана ошибка");
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childTransaction() {
    userRepository.save(user2);
    throw new RuntimeException();  // Откатит только user2
}

// Результат: user1 сохранён, user2 нет

Точка сохранения (Savepoint)

@Transactional
public void complexOperation() {
    userRepository.save(user1);
    
    // Создаём точку сохранения
    Object savepoint = TransactionAspectSupport
        .currentTransactionStatus()
        .createSavepoint();
    
    try {
        userRepository.save(user2);
        // Некорректная операция
        Integer.parseInt("abc");
    } catch (Exception e) {
        // Откатываем только до точки сохранения
        TransactionAspectSupport
            .currentTransactionStatus()
            .rollbackToSavepoint(savepoint);
    }
}

Правила и best practices

  1. Используй unchecked exceptions для отката автоматический
  2. Не ловй исключения в @Transactional методах без пробрасывания
  3. Явно отката через setRollbackOnly() если перехватываешь
  4. Используй REQUIRES_NEW для независимых вложенных операций
  5. Логируй перед откатом для отладки

Понимание механизма откатов критично для построения надёжных систем работы с БД.