← Назад к вопросам
Как открыть новую транзакцию при вызове метода @Service
2.3 Middle🔥 181 комментариев
#Spring Boot и Spring Data#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Управление транзакциями в Spring (@Transactional)
Основное понятие
@Transactional — аннотация Spring, которая автоматически открывает новую транзакцию для метода, управляет коммитом и откатом. Это одна из ключевых фич Spring для работы с БД.
Как работает @Transactional
Spring использует AOP прокси-паттерн: при вызове метода с аннотацией @Transactional, прокси перехватывает вызов, открывает транзакцию, выполняет метод, затем коммитит или откатывает транзакцию.
// Без @Transactional
public void createOrder(Order order) { // Запросы выполняются без транзакции
repository.save(order);
}
// С @Transactional
@Transactional
public void createOrder(Order order) { // Все запросы в одной транзакции
repository.save(order);
// Если исключение — откат
// Если успех — коммит
}
Базовый синтаксис
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private UserRepository userRepository;
// Простой случай — новая транзакция
@Transactional
public Order createOrder(Long userId, OrderRequest request) {
User user = userRepository.findById(userId);
Order order = new Order(user, request.getAmount());
return orderRepository.save(order);
}
}
Параметры @Transactional
1. readOnly
Оптимизация для операций только на чтение:
@Transactional(readOnly = true)
public List<Order> getAllOrders() {
// Spring может применить оптимизации на уровне БД
// Например, на уровне изоляции
return orderRepository.findAll();
}
2. isolation
Уровень изоляции транзакции:
@Transactional(isolation = Isolation.SERIALIZABLE)
public void transferMoney(Long fromId, Long toId, double amount) {
// Максимальная изоляция, предотвращает все аномалии
}
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void fastRead() {
// Быстрое чтение, возможны грязные чтения
}
Уровни (от слабого к сильному):
READ_UNCOMMITTED— видит незакоммиченные измененияREAD_COMMITTED— видит только закоммиченныеREPEATABLE_READ— повторяемое чтениеSERIALIZABLE— полная изоляция
3. propagation
Поведение при вложенных вызовах:
@Transactional
public void outerMethod() {
innerMethod(); // Что произойдёт с транзакцией?
}
@Transactional(propagation = Propagation.REQUIRED)
public void innerMethod() {
// REQUIRED (по умолчанию): использует существующую или создаёт новую
// Если outerMethod открыл транзакцию, innerMethod её использует
}
Все варианты propagation:
// REQUIRED (по умолчанию)
@Transactional(propagation = Propagation.REQUIRED)
public void required() {
// Использует существующую или создаёт новую
}
// REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNew() {
// ВСЕГДА создаёт новую транзакцию
// Даже если уже есть, приостанавливает текущую
}
// NESTED
@Transactional(propagation = Propagation.NESTED)
public void nested() {
// Создаёт nested транзакцию (savepoint)
// Если ошибка — откат только вложенной
}
// NEVER
@Transactional(propagation = Propagation.NEVER)
public void never() {
// Выбросит исключение, если есть активная транзакция
}
// NOT_SUPPORTED
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupported() {
// Приостанавливает текущую транзакцию
}
// SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void supports() {
// Использует существующую, если нет — выполняет без транзакции
}
// MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void mandatory() {
// Требует существующую транзакцию, иначе ошибка
}
4. rollbackFor и noRollbackFor
@Service
public class PaymentService {
// По умолчанию откатывается на RuntimeException и Error
@Transactional
public void processPayment(Payment payment) {
if (payment.getAmount() < 0) {
throw new IllegalArgumentException("Invalid amount");
// Откат
}
}
// Откатывать на конкретных исключениях
@Transactional(rollbackFor = {PaymentException.class, IOException.class})
public void processWithSpecificRollback() {
// Откатится на PaymentException или IOException
}
// НЕ откатывать на конкретных исключениях
@Transactional(noRollbackFor = ClientException.class)
public void processIgnoringClient() {
// Не откатится на ClientException
}
// Откатывать на Checked исключениях
@Transactional(rollbackFor = Exception.class)
public void processWithCheckedException() throws Exception {
// Откатится на любом Exception
}
}
5. timeout
@Transactional(timeout = 30) // 30 секунд
public void longOperation() {
// Если выполняется дольше 30 сек — выбросит TimedOutException
}
Практические примеры
Пример 1: Базовая операция
@Service
public class OrderService {
private final OrderRepository orderRepo;
private final PaymentService paymentService;
public OrderService(OrderRepository orderRepo, PaymentService paymentService) {
this.orderRepo = orderRepo;
this.paymentService = paymentService;
}
@Transactional
public Order placeOrder(Long userId, OrderRequest request) {
// 1. Создаём заказ
Order order = new Order(userId, request.getAmount());
orderRepo.save(order);
// 2. Обрабатываем платёж
// Если платёж не удался — откат ВСЕГО (включая save заказа)
paymentService.processPayment(order);
return order; // Успех — коммит транзакции
}
}
Пример 2: Вложенные транзакции
@Service
public class OrderService {
private final OrderRepository orderRepo;
private final NotificationService notificationService;
@Transactional
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
orderRepo.save(order);
// Отправляем уведомление в отдельной транзакции
notificationService.sendOrderConfirmation(order);
return order;
}
}
@Service
public class NotificationService {
private final NotificationRepo notifRepo;
// REQUIRES_NEW: даже если основная транзакция откатится,
// уведомление будет сохранено
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendOrderConfirmation(Order order) {
Notification notification = new Notification(order);
notifRepo.save(notification);
// Отправляем email/SMS
}
}
Пример 3: Откат при исключении
@Service
public class TransferService {
private final AccountRepository accountRepo;
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepo.findById(fromId);
Account to = accountRepo.findById(toId);
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException("Not enough money");
// Откат — от и до остаются в исходном состоянии
}
from.withdraw(amount);
to.deposit(amount);
accountRepo.save(from);
accountRepo.save(to);
// Коммит обеих операций атомарно
}
}
Пример 4: readOnly оптимизация
@Service
public class ReportService {
private final OrderRepository orderRepo;
private final UserRepository userRepo;
@Transactional(readOnly = true)
public ReportDto generateReport(LocalDate from, LocalDate to) {
// Spring может применить оптимизации для чтения
List<Order> orders = orderRepo.findByDateRange(from, to);
List<User> users = userRepo.findAll();
return new ReportDto(orders, users);
}
// БЕЗ readOnly для изменения
@Transactional
public void updateReport(Report report) {
reportRepo.save(report);
}
}
Пример 5: Обработка исключений
@Service
public class UserService {
private final UserRepository userRepo;
// Откатывать на BusinessException
@Transactional(rollbackFor = BusinessException.class)
public User createUser(UserRequest request) throws BusinessException {
if (userRepo.existsByEmail(request.getEmail())) {
throw new BusinessException("Email already exists");
// Откат
}
User user = new User(request);
return userRepo.save(user);
}
// НЕ откатывать на ClientException
@Transactional(noRollbackFor = ClientException.class)
public void processUserData(UserData data) throws ClientException {
if (data.isInvalid()) {
throw new ClientException("Invalid data"); // НЕ откат
}
// Обработка
}
}
Частые ошибки
Ошибка 1: @Transactional на классе вместо методов
// ❌ Плохо
@Service
@Transactional
public class UserService {
public void readUser() { } // Тоже получит @Transactional
public void writeUser() { }
}
// ✅ Хорошо
@Service
public class UserService {
@Transactional(readOnly = true)
public void readUser() { }
@Transactional
public void writeUser() { }
}
Ошибка 2: Вызов из того же класса
@Service
public class OrderService {
@Transactional
public void createOrder() {
// ...
processPayment(); // @Transactional НЕ сработает!
}
@Transactional
public void processPayment() { }
}
// Правильно: через另инъецированный сервис
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
@Transactional
public void createOrder() {
paymentService.processPayment(); // Сработает @Transactional
}
}
Ошибка 3: Неправильный propagation
// ❌ Если нужна отдельная транзакция
@Transactional(propagation = Propagation.REQUIRED)
public void audit() { }
// ✅ Правильно
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void audit() {
// Создаст новую транзакцию, даже если есть существующая
}
Конфигурация TransactionManager
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
Лучшие практики
- Используй @Transactional только на сервисах
- Указывай readOnly для операций чтения — оптимизация
- Используй конструктор вместо field injection — явность
- Откатывай на ошибках — используй rollbackFor
- Избегай больших транзакций — разбивай на меньшие
- Используй REQUIRES_NEW для audit логов — они сохранятся даже при откате
- Не вызывай @Transactional методы из того же класса — используй инъекцию
@Transactional — это один из самых важных инструментов в Spring для гарантии целостности данных.