Что такое Transaction Propagation в Spring?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Transaction Propagation в Spring
Transaction Propagation — это механизм, который определяет, как должны вести себя транзакции, когда один транзакционный метод вызывает другой транзакционный метод. Это критически важно для контроля границ транзакций и поведения вложенных вызовов.
Основная проблема
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// Логика создания заказа
orderRepository.save(order);
emailService.sendConfirmation(order); // Вложенный вызов
}
}
@Service
public class EmailService {
@Transactional
public void sendConfirmation(Order order) {
// Отправка email
}
}
Вопрос: должны ли оба метода работать в одной транзакции или в отдельных? Это определяет Transaction Propagation.
Типы Propagation
1. REQUIRED (по умолчанию)
Если существует транзакция, использовать её. Если нет — создать новую.
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB(); // Будет использовать ту же транзакцию
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// Работает в той же транзакции
}
Поведение:
- Если methodA() уже в транзакции, methodB() использует её
- Если methodA() не в транзакции, для methodB() создаётся новая
Это самый частый выбор для большинства случаев.
2. REQUIRES_NEW
Всегда создавать новую транзакцию, приостанавливая текущую.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void criticalOperation() {
// Работает в НОВОЙ транзакции
// Даже если вызвана из другого транзакционного метода
}
@Transactional
public void methodA() {
try {
criticalOperation(); // Создаёт новую транзакцию
} catch (Exception e) {
// criticalOperation() успела закоммититься или откатиться независимо
}
}
Диаграмма:
methodA транзакция: [======== SUSPENDED ========]
criticalOperation: [=====REQUIRES_NEW=====]
Это полезно, когда нужна независимая транзакция, которая должна завершиться независимо от внешней.
3. NESTED
Создаёт вложенную транзакцию (savepoint), если поддерживается БД.
@Transactional
public void methodA() {
// Точка сохранения 1
methodB(); // Может откатиться до точки сохранения
}
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
// Работает в savepoint
}
Диаграмма:
methodA: [===============================]
methodB: [===NESTED===]
SP1 SP2 Commit/Rollback
Если methodB() провалится, только её работа откатится, но methodA() продолжит работу.
4. SUPPORTS
Использовать транзакцию, если она существует, иначе работать без неё.
@Transactional(propagation = Propagation.SUPPORTS)
public void readOnlyOperation() {
// Может работать в транзакции или без неё
return getUserData();
}
Это полезно для read-only методов, которые могут быть вызваны как из транзакционных, так и из не-транзакционных методов.
5. NOT_SUPPORTED
Работать БЕЗ транзакции, приостанавливая текущую если она есть.
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void nonTransactionalOperation() {
// Работает БЕЗ транзакции, даже если вызвана из @Transactional метода
}
@Transactional
public void methodA() {
nonTransactionalOperation(); // Текущая транзакция приостанавливается
}
6. MANDATORY
Требует существующую транзакцию. Если её нет — выбросить исключение.
@Transactional(propagation = Propagation.MANDATORY)
public void operationRequiresTransaction() {
// Выбросит TransactionRequiredException, если нет активной транзакции
}
7. NEVER
Не должна быть в транзакции. Если транзакция существует — выбросить исключение.
@Transactional(propagation = Propagation.NEVER)
public void operationForbidddenTransaction() {
// Выбросит IllegalTransactionStateException, если есть активная транзакция
}
Практические примеры
Пример 1: Логирование независимо от основной транзакции
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private AuditService auditService;
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
// Логирование выполнится независимо
auditService.logAction("Order created: " + order.getId());
}
}
@Service
public class AuditService {
@Autowired
private AuditRepository auditRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAction(String message) {
// Запись в лог БД в отдельной транзакции
// Даже если createOrder() откатится, лог останется
auditRepository.save(new AuditLog(message));
}
}
Пример 2: Обработка ошибок с вложенной транзакцией
@Service
public class PaymentService {
@Transactional
public void processPayment(Payment payment) {
paymentRepository.save(payment);
try {
notifyCustomer(payment);
} catch (Exception e) {
// Ошибка уведомления не откатит платёж
}
}
@Transactional(propagation = Propagation.NESTED)
public void notifyCustomer(Payment payment) {
emailService.sendConfirmation(payment);
}
}
Пример 3: Read-only операции
@Service
public class UserService {
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public User getUser(Long id) {
// Может работать в транзакции для консистентности
// или без неё для оптимизации
return userRepository.findById(id);
}
@Transactional
public void updateUser(User user) {
getUser(user.getId()); // Использует транзакцию от updateUser
userRepository.save(user);
}
}
Таблица всех типов Propagation
| Тип | Текущая транзакция | Ново ткань | Поведение |
|---|---|---|---|
| REQUIRED | EXISTS | Reuse | Использует существующую |
| REQUIRED | NONE | Create | Создаёт новую |
| REQUIRES_NEW | EXISTS | Suspend+Create | Приостанавливает и создаёт |
| REQUIRES_NEW | NONE | Create | Создаёт новую |
| NESTED | EXISTS | Savepoint | Создаёт savepoint |
| NESTED | NONE | Create | Создаёт новую |
| SUPPORTS | EXISTS | Reuse | Использует существующую |
| SUPPORTS | NONE | None | Работает без транзакции |
| NOT_SUPPORTED | EXISTS | Suspend | Приостанавливает текущую |
| NOT_SUPPORTED | NONE | None | Работает без транзакции |
| MANDATORY | EXISTS | Reuse | Использует существующую |
| MANDATORY | NONE | Error | Выбрасывает исключение |
| NEVER | EXISTS | Error | Выбрасывает исключение |
| NEVER | NONE | None | Работает без транзакции |
Рекомендации
Используйте REQUIRED (по умолчанию) для большинства операций — это предсказуемо и просто.
Используйте REQUIRES_NEW для независимых операций типа логирования, аудита или отправки уведомлений.
Используйте NESTED когда поддерживается БД (PostgreSQL, Oracle) и нужна частичная откат.
Используйте SUPPORTS для read-only операций.
Избегайте MANDATORY и NEVER в обычном коде — они усложняют отладку.
Transaction Propagation — мощный механизм для контроля границ транзакций в сложных приложениях.