← Назад к вопросам
Как обеспечить возврат транзакции при проверяемом исключении
1.8 Middle🔥 111 комментариев
#ООП#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Обеспечение возврата транзакции при проверяемом исключении
Проблема
В Spring Framework по умолчанию откат (rollback) транзакции происходит только при возникновении непроверяемых исключений (unchecked exceptions). Проверяемые исключения (checked exceptions) по умолчанию не вызывают откат, так как фреймворк рассматривает их как ожидаемые и обработанные ошибки.
Решение 1: Аннотация Transactional с rollbackFor
Это самый простой и рекомендуемый способ:
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class)
public void processOrder(Order order) throws Exception {
orderRepository.save(order);
if (order.getAmount() < 0) {
throw new InvalidOrderException("Invalid amount");
}
paymentService.processPayment(order);
}
}
public class InvalidOrderException extends Exception {
public InvalidOrderException(String message) {
super(message);
}
}
Решение 2: Откат для нескольких исключений
@Service
public class PaymentService {
@Transactional(rollbackFor = {
PaymentException.class,
DatabaseException.class,
ValidationException.class
})
public void handlePayment(Payment payment)
throws PaymentException, DatabaseException {
validatePayment(payment);
savePayment(payment);
if (!isPaymentValid(payment)) {
throw new PaymentException("Payment validation failed");
}
}
}
Решение 3: Использование noRollbackFor
@Service
public class UserService {
@Transactional(rollbackFor = Exception.class,
noRollbackFor = UserAlreadyExistsException.class)
public void registerUser(User user) throws UserAlreadyExistsException {
if (userRepository.exists(user.getEmail())) {
throw new UserAlreadyExistsException("User already exists");
}
userRepository.save(user);
}
}
Решение 4: Явный откат через TransactionAspectSupport
import org.springframework.transaction.support.TransactionAspectSupport;
@Service
public class ComplexTransactionService {
@Transactional
public void complexOperation(Data data) throws Exception {
try {
processData(data);
} catch (SomeCheckedException e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
System.out.println("Rollback requested");
throw e;
}
}
}
Решение 5: Использование PlatformTransactionManager
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Service
public class ManualTransactionService {
private final PlatformTransactionManager transactionManager;
public ManualTransactionService(PlatformTransactionManager tm) {
this.transactionManager = tm;
}
public void executeWithManualTransaction() throws ProcessingException {
DefaultTransactionDefinition definition =
new DefaultTransactionDefinition();
TransactionStatus status =
transactionManager.getTransaction(definition);
try {
performOperation();
transactionManager.commit(status);
} catch (ProcessingException e) {
transactionManager.rollback(status);
System.out.println("Transaction rolled back");
throw e;
}
}
}
Решение 6: Try-Catch с явным rollback маркером
@Service
public class OrderProcessingService {
private final OrderRepository orderRepository;
public void processOrderWithValidation(Order order)
throws ValidationException {
try {
validateOrder(order);
orderRepository.save(order);
} catch (ValidationException e) {
markTransactionForRollback();
throw e;
}
}
@Transactional
private void markTransactionForRollback() {
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}
Решение 7: Практический пример - Перевод денег
@Service
public class BankTransferService {
private final AccountRepository accountRepository;
private final TransactionRepository transactionRepository;
@Transactional(rollbackFor = InsufficientFundsException.class)
public void transferMoney(String fromAccountId,
String toAccountId,
BigDecimal amount)
throws InsufficientFundsException,
AccountNotFoundException {
Account fromAccount = accountRepository.findById(fromAccountId)
.orElseThrow(() ->
new AccountNotFoundException("From account not found"));
Account toAccount = accountRepository.findById(toAccountId)
.orElseThrow(() ->
new AccountNotFoundException("To account not found"));
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException(
"Insufficient funds. Available: " +
fromAccount.getBalance());
}
fromAccount.withdraw(amount);
toAccount.deposit(amount);
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
Transaction transaction = new Transaction(
fromAccountId, toAccountId, amount,
TransactionStatus.SUCCESS);
transactionRepository.save(transaction);
}
}
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
Решение 8: TransactionTemplate
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class GenericTransactionService {
private final TransactionTemplate transactionTemplate;
public GenericTransactionService(
TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void executeWithRollback(ThrowingRunnable operation)
throws Exception {
try {
transactionTemplate.execute(status -> {
try {
operation.run();
return null;
} catch (Exception e) {
status.setRollbackOnly();
throw new RuntimeException(e);
}
});
} catch (RuntimeException e) {
if (e.getCause() instanceof Exception) {
throw (Exception) e.getCause();
}
throw e;
}
}
}
@FunctionalInterface
public interface ThrowingRunnable {
void run() throws Exception;
}
Сравнение подходов
| Подход | Преимущества | Недостатки |
|---|---|---|
| rollbackFor | Простой, декларативный | Требует аннотирования |
| TransactionAspectSupport | Точное управление | Требует явного кода |
| PlatformTransactionManager | Полный контроль | Многословный |
| TransactionTemplate | Функциональный подход | Требует lambda |
Ключевые моменты
- rollbackFor = Exception.class откатит ВСЕ исключения
- rollbackFor = {ExceptionA.class, ExceptionB.class} для конкретных
- noRollbackFor исключает определенные исключения из отката
- setRollbackOnly() помечает транзакцию для отката внутри другой транзакции
- Проверяемые исключения требуют явного указания для отката
Заключение
Для обеспечения отката транзакции при проверяемом исключении используйте @Transactional(rollbackFor = YourCheckedException.class). Это простой, надежный и читаемый способ, соответствующий Spring best practices.