Какие знаешь причины отмены транзакции в Spring?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Причины отмены транзакции в Spring
Отмена (откат) транзакции в Spring — это критически важный механизм обеспечения консистентности данных. Понимание причин, по которым происходит откат, необходимо для построения надежных приложений.
Явные проверенные исключения (Checked Exceptions)
По умолчанию Spring откатывает транзакцию при непроверяемых исключениях (RuntimeException). Для явных исключений необходимо явно указать откат:
@Service
public class PaymentService {
@Autowired
private PaymentRepository paymentRepository;
@Transactional(rollbackFor = PaymentException.class)
public void processPayment(Payment payment) throws PaymentException {
if (payment.getAmount() < 0) {
throw new PaymentException("Сумма не может быть отрицательной");
}
paymentRepository.save(payment);
}
}
RuntimeException и его подклассы
Все наследники RuntimeException автоматически вызывают откат транзакции:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(String email) {
if (userRepository.existsByEmail(email)) {
throw new IllegalArgumentException("Пользователь уже существует");
}
User user = new User();
user.setEmail(email);
userRepository.save(user);
}
}
NullPointerException и другие системные ошибки
Любые непроверяемые исключения вызывают откат:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public void createOrder(Long userId, Order order) {
User user = userService.findUser(userId);
user.addOrder(order);
orderRepository.save(order);
}
}
Явный откат через TransactionStatus
Программное управление откатом при низкоуровневой работе с транзакциями:
@Service
public class BulkOperationService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private UserRepository userRepository;
public void importUsers(List<User> users) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
int count = 0;
for (User user : users) {
userRepository.save(user);
count++;
if (count % 1000 == 0) {
transactionManager.rollback(status);
status = transactionManager.getTransaction(def);
}
}
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
noRollbackFor для избирательного отката
Используется для исключений, при которых откат не должен происходить:
@Service
public class NotificationService {
@Autowired
private EmailService emailService;
@Autowired
private UserRepository userRepository;
@Transactional(noRollbackFor = EmailSendException.class)
public void registerUser(User user) {
userRepository.save(user);
try {
emailService.sendWelcomeEmail(user.getEmail());
} catch (EmailSendException e) {
log.error("Не удалось отправить письмо", e);
}
}
}
Timeout и deadlock
Датабаза сама откатывает транзакцию при таймаутах и deadlock'ах:
@Service
public class InventoryService {
@Autowired
private InventoryRepository inventoryRepository;
@Transactional(timeout = 10)
public void updateInventory(List<Long> itemIds) {
for (Long id : itemIds) {
Inventory item = inventoryRepository.findById(id).orElseThrow();
item.setQuantity(item.getQuantity() - 1);
inventoryRepository.save(item);
}
}
}
Нарушение ограничений базы данных
Уникальные ключи, внешние ключи и check constraints вызывают откат:
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void createAccount(Account account) {
accountRepository.save(account);
}
}
Пользовательские исключения с откатом
Определение собственных исключений для контролируемого отката:
public class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException(String message) {
super(message);
}
}
@Service
public class AccountTransferService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepository.findById(fromId).orElseThrow();
Account to = accountRepository.findById(toId).orElseThrow();
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException("Недостаточно средств");
}
from.withdraw(amount);
to.deposit(amount);
accountRepository.saveAll(List.of(from, to));
}
}
Практические рекомендации
- Используй @Transactional(rollbackFor = CustomException.class) для явных исключений
- Помни, что откат происходит только для непроверяемых исключений по умолчанию
- Указывай timeout для долгих операций чтобы избежать deadlock'ов
- Тестируй откат транзакций с помощью @Transactional в тестах
- Логируй причины отката для отладки проблем с данными