Какие знаешь атрибуты Propagation в @Transactional?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Propagation в @Transactional: все типы и примеры
Propagation - это атрибут аннотации @Transactional в Spring, который определяет как должна вести себя транзакция когда вызывается метод в другом транзакционном методе. Это критически важно для правильного управления транзакциями.
1. REQUIRED (по умолчанию)
Самый частый тип - если транзакция уже существует, использовать её; если нет, создать новую:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private AuditService auditService;
// REQUIRED - использует текущую транзакцию или создаёт новую
@Transactional(propagation = Propagation.REQUIRED)
public User createUser(String email, String name) {
User user = new User(email, name);
User savedUser = userRepository.save(user);
// Эта строка выполняется в ОДНОЙ транзакции с createUser
auditService.logUserCreation(savedUser);
return savedUser;
}
}
@Service
public class AuditService {
@Autowired
private AuditLogRepository auditLogRepository;
// REQUIRED - присоединяется к транзакции из UserService
@Transactional(propagation = Propagation.REQUIRED)
public void logUserCreation(User user) {
AuditLog log = new AuditLog();
log.setAction("USER_CREATED");
log.setUserId(user.getId());
auditLogRepository.save(log);
}
}
Поведение: Если что-то пойдёт не так в logUserCreation(), вся транзакция (включая создание пользователя) откатится.
2. REQUIRES_NEW
Создаёт НОВУЮ транзакцию, даже если уже существует. Текущая транзакция приостанавливается:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private NotificationService notificationService;
@Transactional(propagation = Propagation.REQUIRED)
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
Order savedOrder = orderRepository.save(order);
// REQUIRES_NEW - создаёт новую транзакцию
try {
notificationService.sendConfirmationEmail(savedOrder);
} catch (Exception e) {
// Даже если отправка письма упадёт, заказ будет сохранён
logger.warn("Failed to send confirmation email", e);
}
return savedOrder;
}
}
@Service
public class NotificationService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendConfirmationEmail(Order order) {
// Эта транзакция НЕ зависит от транзакции OrderService
// Если она упадёт - заказ всё равно сохранится
EmailLog emailLog = new EmailLog();
emailLog.setOrderId(order.getId());
emailLogRepository.save(emailLog);
emailService.send(order.getCustomerEmail());
}
}
Использование: Когда некоторые операции должны быть независимыми (логирование, уведомления, аналитика).
3. SUPPORTS
Если транзакция существует, использовать её; если нет, выполнить без транзакции:
@Service
public class UserQueryService {
@Autowired
private UserRepository userRepository;
// SUPPORTS - использует транзакцию если она уже есть
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public User getUserById(UUID id) {
return userRepository.findById(id).orElse(null);
}
// Сценарий 1: Вызов из @Transactional метода
@Transactional
public void processUser(UUID userId) {
User user = getUserById(userId); // Использует текущую транзакцию
}
// Сценарий 2: Вызов из обычного метода
public User getPublicUser(UUID userId) {
return getUserById(userId); // Выполняется БЕЗ транзакции
}
}
4. MANDATORY
Метод ТРЕБУЕТ наличие активной транзакции. Если её нет - выбросит исключение:
@Service
public class PaymentService {
@Autowired
private PaymentRepository paymentRepository;
// MANDATORY - требует транзакцию
@Transactional(propagation = Propagation.MANDATORY)
public void recordPayment(Payment payment) {
paymentRepository.save(payment);
}
// Правильно - вызов из @Transactional метода
@Transactional
public void processOrder(Order order) {
Payment payment = new Payment(order);
recordPayment(payment); // OK - транзакция существует
}
// Неправильно - выбросит InvalidTransactionStateException
public void asyncPayment(Payment payment) {
recordPayment(payment); // ERROR - нет транзакции!
}
}
5. NOT_SUPPORTED
Если транзакция существует, она будет приостановлена и метод выполнится БЕЗ транзакции:
@Service
public class AnalyticsService {
@Autowired
private AnalyticsRepository analyticsRepository;
// NOT_SUPPORTED - выполняется БЕЗ транзакции
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void logAnalyticsEvent(String eventName) {
// Это записывается напрямую в БД, не в транзакции
// Так что даже если основная транзакция откатится - событие будет записано
AnalyticsEvent event = new AnalyticsEvent(eventName);
analyticsRepository.save(event);
}
@Transactional
public void processPayment(Order order) {
// Основная логика в транзакции
updateOrderStatus(order);
// Логирование выполняется БЕЗ транзакции
logAnalyticsEvent("PAYMENT_PROCESSED");
}
}
6. NEVER
Метод НЕ должен выполняться в транзакции. Если транзакция существует - выбросит исключение:
@Service
public class BackgroundTaskService {
@Autowired
private TaskRepository taskRepository;
// NEVER - НЕ должна быть транзакция
@Transactional(propagation = Propagation.NEVER)
public void executeBackgroundTask(Task task) {
taskRepository.save(task);
}
// Правильно - вызов БЕЗ транзакции
public void scheduleTask(Task task) {
executeBackgroundTask(task); // OK
}
// Неправильно - выбросит IllegalTransactionStateException
@Transactional
public void invalidCall(Task task) {
executeBackgroundTask(task); // ERROR!
}
}
7. NESTED
Использует savepoints для вложенных транзакций (только с определёнными БД):
@Service
public class NestedTransactionService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ItemService itemService;
@Transactional(propagation = Propagation.REQUIRED)
public Order createOrderWithItems(OrderRequest request) {
Order order = new Order(request);
orderRepository.save(order);
for (ItemRequest itemRequest : request.getItems()) {
try {
itemService.addItem(order, itemRequest);
} catch (Exception e) {
logger.warn("Failed to add item, continuing", e);
// Ошибка в одном item не отменяет весь заказ
}
}
return order;
}
@Transactional(propagation = Propagation.NESTED)
public void addItem(Order order, ItemRequest itemRequest) {
Item item = new Item(order, itemRequest);
itemRepository.save(item);
}
}
Таблица сравнения Propagation типов
| Тип | Новая ТХ | Если ТХ существует | Использование |
|---|---|---|---|
| REQUIRED | Да | Использует существующую | Default, большинство случаев |
| REQUIRES_NEW | Да | Создаёт новую | Независимые операции |
| SUPPORTS | Нет | Использует если есть | Read-only операции |
| MANDATORY | - | Требует наличие | Критичные операции |
| NOT_SUPPORTED | Нет | Приостанавливает | Логирование, аналитика |
| NEVER | Нет | Выбросит ошибку | Фоновые задачи |
| NESTED | Да | Savepoint | Вложенные ТХ |
Best Practices
✓ REQUIRED для основной бизнес-логики ✓ REQUIRES_NEW для независимых операций (логирование, уведомления) ✓ SUPPORTS с readOnly=true для read-only методов ✓ MANDATORY для критичного кода, где ошибка без транзакции - это ошибка архитектуры ✓ NOT_SUPPORTED для аналитики, которая должна работать независимо ✓ Всегда устанавливайте readOnly=true для методов, которые только читают ✓ Используйте REQUIRES_NEW для операций, которые должны успешно завершиться даже если основная транзакция упадёт ✓ Помните, что NESTED работает не на всех БД (SQLServer, Oracle да, MySQL нет)
Правильный выбор Propagation - это ключ к надёжной и предсказуемой работе транзакций в приложении.