← Назад к вопросам
Что произойдет при ошибке во время выполнения @Transactional метода?
2.0 Middle🔥 231 комментариев
#Spring Framework#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
@Transactional: обработка ошибок и откат
Аннотация @Transactional — это механизм Spring Framework для управления транзакциями базы данных. Когда ошибка возникает во время выполнения метода, помеченного @Transactional, происходит откат транзакции (rollback) с соблюдением определённых правил.
1. Поведение при исключениях по умолчанию
Checked исключения (не вызывают откат по умолчанию)
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
// ❌ Проблема: checked exception НЕ вызывает откат
@Transactional
public void createOrder(Order order) throws IOException {
orderRepository.save(order);
// Даже если IOException выбросится, транзакция КОММИТИТСЯ
writeToFile("order_" + order.getId() + ".txt");
}
private void writeToFile(String filename) throws IOException {
throw new IOException("Ошибка при записи файла");
}
}
// Результат:
// 1. Order сохранён в БД (COMMIT)
// 2. IOException выброшена в вызывающий код
// 3. Данные остаются в БД (несогласованное состояние!)
// ✅ Решение 1: явно указать rollbackFor
@Transactional(rollbackFor = IOException.class)
public void createOrder(Order order) throws IOException {
orderRepository.save(order);
writeToFile("order_" + order.getId() + ".txt"); // Вызовет ROLLBACK
}
// ✅ Решение 2: обернуть в unchecked exception
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
try {
writeToFile("order_" + order.getId() + ".txt");
} catch (IOException e) {
throw new RuntimeException("Ошибка при создании заказа", e);
}
}
Unchecked исключения (вызывают откат по умолчанию)
@Service
public class PaymentService {
@Autowired
private PaymentRepository paymentRepository;
// ✅ Unchecked exception вызывает откат автоматически
@Transactional
public void processPayment(Payment payment) {
paymentRepository.save(payment); // Save вставляет в БД
// Ошибка! Это вызовет ROLLBACK всей транзакции
if (payment.getAmount() > 10000) {
throw new IllegalArgumentException("Сумма слишком большая");
}
}
}
// Результат:
// 1. RuntimeException выброшена
// 2. Все изменения в транзакции откатываются (ROLLBACK)
// 3. Payment не сохранена в БД
// 4. БД остаётся в консистентном состоянии
2. Откат транзакции (Rollback)
@Service
public class TransactionRollbackDemo {
@Autowired
private UserRepository userRepository;
@Autowired
private AccountRepository accountRepository;
// Полный откат при ошибке
@Transactional
public void transferMoney(User from, User to, BigDecimal amount) {
// Шаг 1: уменьшить баланс from
from.setBalance(from.getBalance().subtract(amount));
userRepository.save(from); // BUFFERED в транзакции
// Шаг 2: увеличить баланс to
to.setBalance(to.getBalance().add(amount));
userRepository.save(to); // BUFFERED в транзакции
// Шаг 3: логирование может вызвать ошибку
if (amount.compareTo(BigDecimal.valueOf(5000)) > 0) {
throw new RuntimeException("Трансфер не прошёл (сумма слишком большая)");
// Все 2 save() откатываются — деньги остаются на месте!
}
// Если ошибки нет — обе save() коммитятся
}
}
// ✅ Пример с обработкой ошибок
@Service
public class TransactionWithErrorHandling {
@Autowired
private UserRepository userRepository;
@Transactional
public boolean transferMoney(User from, User to, BigDecimal amount) {
try {
from.setBalance(from.getBalance().subtract(amount));
userRepository.save(from);
to.setBalance(to.getBalance().add(amount));
userRepository.save(to);
return true; // Транзакция коммитится
} catch (RuntimeException e) {
// Транзакция откатывается автоматически
// Но мы можем залогировать ошибку
System.err.println("Ошибка трансфера: " + e.getMessage());
return false;
}
}
}
3. Явное управление откатом
@Service
public class ManualRollback {
@Autowired
private UserRepository userRepository;
@Transactional
public void processUser(User user) {
userRepository.save(user);
// Получить текущую транзакцию
TransactionStatus status = TransactionAspectSupport.currentTransactionStatus();
if (shouldRollback(user)) {
// Явно выполнить откат
status.setRollbackOnly(); // Пометить транзакцию на откат
}
}
private boolean shouldRollback(User user) {
return user.getAge() < 18; // Пример условия
}
}
// Результат:
// 1. User сохранён в памяти Spring
// 2. setRollbackOnly() пометит транзакцию
// 3. При выходе из метода ALL изменения откатываются
// 4. User не будет в БД
4. Обработка определённых исключений
@Service
public class SelectiveRollback {
@Autowired
private OrderRepository orderRepository;
// Откатывать только на определённые исключения
@Transactional(
rollbackFor = {PaymentException.class, InventoryException.class},
noRollbackFor = {ValidationException.class} // Эти ошибки не откатывают
)
public void createOrder(Order order) throws Exception {
orderRepository.save(order);
// Это вызовет ROLLBACK
if (!order.isValid()) {
throw new PaymentException("Платёж не прошёл");
}
// Это НЕ вызовет ROLLBACK (noRollbackFor)
if (order.getItems().isEmpty()) {
throw new ValidationException("Заказ пуст");
}
}
}
// Результат:
// - PaymentException → ROLLBACK (откат)
// - ValidationException → COMMIT (коммит)
5. Откат в случае ошибки БД
@Service
public class DatabaseErrorHandling {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(User user) {
userRepository.save(user);
// Если поле имеет UNIQUE constraint
User duplicate = new User();
duplicate.setEmail(user.getEmail()); // Такой email уже существует
userRepository.save(duplicate);
// DataIntegrityViolationException → ROLLBACK
}
}
// Spring поймает SQLIntegrityConstraintViolationException
// и откатит ВСЮ транзакцию, включая первый save()
6. Откат при вложенных транзакциях
@Service
public class NestedTransactions {
@Autowired
private UserRepository userRepository;
@Autowired
private AuditRepository auditRepository;
@Transactional
public void updateUser(User user) {
userRepository.save(user);
try {
logAudit(user);
} catch (Exception e) {
// Ошибка в логировании
System.err.println("Ошибка логирования: " + e.getMessage());
}
}
// Вложенная транзакция
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAudit(User user) {
auditRepository.save(new AuditLog(user));
throw new RuntimeException("Ошибка при логировании"); // Откатит ТОЛЬКО logAudit
}
}
// Результат с REQUIRES_NEW:
// 1. updateUser открывает транзакцию
// 2. userRepository.save() выполняется
// 3. logAudit открывает НОВУЮ транзакцию
// 4. auditRepository.save() выполняется в новой транзакции
// 5. RuntimeException откатывает ТОЛЬКО новую транзакцию (logAudit)
// 6. updateUser коммитится (user сохранён)
// 7. logAudit откатывается (audit НЕ сохранён)
// При REQUIRED (по умолчанию):
// Обе транзакции откатывались бы вместе
@Transactional(propagation = Propagation.REQUIRED) // Использует существующую
public void logAudit(User user) {
// При ошибке откатится ВСЯ цепь транзакций
}
7. Откат в многоэтапных операциях
@Service
public class MultiStepTransaction {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
private NotificationService notificationService;
// Saga Pattern — управление откатом
@Transactional
public void createOrderWithCompensation(Order order) {
try {
// Шаг 1: Создать заказ
orderRepository.save(order);
// Шаг 2: Зарезервировать товары
inventoryRepository.reserve(order.getItems());
// Шаг 3: Отправить уведомление
notificationService.sendConfirmation(order);
} catch (Exception e) {
// При ошибке on step 3: откатываются шаги 1 и 2
// При ошибке БД: откатываются ВСЕ шаги
throw new RuntimeException("Ошибка при создании заказа", e);
}
}
}
Итоговое резюме
| Исключение | По умолчанию | Результат |
|---|---|---|
| Unchecked (RuntimeException) | Откат | ROLLBACK всей транзакции |
| Checked (IOException, etc) | Коммит | COMMIT, несмотря на ошибку |
| С rollbackFor | Откат | ROLLBACK для указанных |
| С noRollbackFor | Коммит | COMMIT, несмотря на ошибку |
| DataIntegrityViolation | Откат | ROLLBACK из-за constraint |
| REQUIRES_NEW | Откат только вложенной | Вложенная откатывается |
Лучшие практики
// ✅ Правильно
@Transactional(rollbackFor = Exception.class) // Откатывать на ВСЕ ошибки
public void processData(Data data) throws Exception {
repository.save(data);
performValidation(data); // Может выбросить Exception
}
// ✅ Правильно — использовать unchecked exceptions в бизнес-логике
@Transactional // По умолчанию откатывает unchecked exceptions
public void transferMoney(User from, User to, BigDecimal amount) {
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException("Недостаточно средств");
}
// ...
}
// ❌ Неправильно — игнорировать ошибки
@Transactional
public void processOrder(Order order) {
try {
repository.save(order);
performPayment(order);
} catch (Exception e) {
// Пустой catch — ошибка не откатит транзакцию
}
}