Ответ
Основные проблемы:
-
Раздельные транзакции для каждой операции
// Без @Transactional на методе сервиса public void processOrder(Order order) { orderRepository.save(order); // Транзакция 1 inventoryRepository.reduceStock(order); // Транзакция 2 // Если reduceStock() упадёт, order уже сохранён! } -
LazyInitializationException при загрузке ленивых ассоциаций
@Entity public class User { @OneToMany(fetch = FetchType.LAZY) private List<Order> orders; // Ленивая коллекция } public void printOrders(Long userId) { User user = userRepository.findById(userId).orElseThrow(); // Транзакция уже закрыта (репозиторий сам открывал) for (Order order : user.getOrders()) { // LazyInitializationException! System.out.println(order); } } -
Отсутствие управления уровнем изоляции и таймаутами
Решение – использование @Transactional:
@Service
public class OrderService {
@Transactional // Открывает транзакцию перед методом, закрывает после
public void processOrder(OrderDto dto) {
// ВСЕ операции в одной транзакции
Order order = orderRepository.save(createOrder(dto));
inventoryRepository.updateStock(order.getItems());
auditRepository.logOrder(order);
// При любом RuntimeException/Error произойдёт rollback всей транзакции
}
@Transactional(readOnly = true) // Оптимизация для чтения
public User getUserWithOrders(Long userId) {
User user = userRepository.findById(userId).orElseThrow();
// Ленивая загрузка работает, т.к. транзакция активна
user.getOrders().size(); // Инициализация коллекции
user.getOrders().forEach(System.out::println);
return user;
}
}
Рекомендации по использованию:
- Размещайте
@Transactionalна уровне сервиса, а не репозитория - Используйте
readOnly = trueдля методов, которые только читают данные - Указывайте
rollbackForдля кастомных исключений - Избегайте вызовов
@Transactionalметодов изнутри того же класса (проблема proxy)