JavaRush /Курсы /Модуль 5. Spring /Rollback и правильное использование транзакций в Spring

Rollback и правильное использование транзакций в Spring

Модуль 5. Spring
6 уровень , 9 лекция
Открыта

В прошлой лекции мы разобрались с типами исключений в транзакциях и базовыми механизмами их обработки в Spring. Теперь пришло время углубиться в практику и разобрать более сложные сценарии использования rollback.


Почему важно правильно настраивать rollback?

Неправильная настройка откатов может привести к серьезным проблемам:

  1. Незавершённые изменения: часть данных может остаться в неконсистентном состоянии, например, при переводе денег между счетами.
  2. Нарушение бизнес-правил: система может сохранить изменения, которые противоречат бизнес-логике (скажем, заказ с превышенным лимитом).
  3. Проблемы масштабирования: в многопользовательских системах неверная обработка транзакций приводит к сложно отслеживаемым ошибкам.

Spring предоставляет гибкие инструменты для управления откатом транзакций. Давайте научимся их использовать.


Практические сценарии использования rollback

В реальных приложениях часто встречаются ситуации, когда стандартного поведения транзакций недостаточно. Рассмотрим на примере интернет-магазина, как можно гибко управлять транзакциями в разных ситуациях.


@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private ProductService productService;

    @Transactional
    public void processOrder(Order order) {
        // Проверяем наличие товара
        if (!productService.isAvailable(order.getProductId())) {
            // Программный откат: явно указываем Spring откатить транзакцию
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            throw new ProductNotAvailableException("Товар недоступен");
        }

        // Сохраняем заказ
        orderRepository.save(order);

        // Проверяем лимиты пользователя
        if (orderRepository.getUserOrderCount(order.getUserId()) > 5) {
            // Автоматический откат: Spring сам откатит транзакцию при исключении
            throw new BusinessException("Превышен лимит заказов");
        }

        // Уменьшаем количество товара и обрабатываем особые случаи
        try {
            productService.decreaseStock(order.getProductId(), order.getQuantity());
        } catch (StockWarningException e) {
            // Логируем предупреждение, но позволяем транзакции завершиться
            notifyManager(e);
        }
    }

    // Пример, где некоторые ошибки не должны отменять всю операцию
    @Transactional(noRollbackFor = {StockWarningException.class})
    public void processOrderWithPartialErrors(Order order) {
        orderRepository.save(order);
        productService.decreaseStock(order.getProductId(), order.getQuantity());

        // Даже если возникнет StockWarningException,
        // заказ останется сохраненным
    }
}

В этом примере мы видим три разных подхода к управлению транзакциями:

  1. Программный откат через setRollbackOnly() — когда нам нужен полный контроль над процессом
  2. Автоматический откат при исключении — стандартное поведение Spring для RuntimeException
  3. Выборочный откат с помощью noRollbackFor — когда некоторые ошибки не должны отменять всю операцию

Каждый подход имеет свои преимущества:

  • Программный откат даёт максимальный контроль
  • Автоматический откат делает код чище и понятнее
  • Выборочный откат позволяет гибко обрабатывать разные типы ошибок

Тестирование rollback

Для проверки правильности работы транзакций Spring предоставляет удобные инструменты тестирования. Давайте рассмотрим, как можно протестировать нашу логику обработки заказов:


@SpringBootTest
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderRepository orderRepository;

    @Test
    @Transactional
    public void testOrderRollbackOnLimitExceeded() {
        // Подготовка
        Order order = new Order("Product A", 1);

        // Действие
        assertThrows(BusinessException.class, () -> {
            orderService.processOrder(order);
        });

        // Проверка
        assertEquals(0, orderRepository.count(),
            "База данных должна быть пустой после отката транзакции");
    }

    @Test
    @Transactional
    public void testPartialRollback() {
        Order order = new Order("Product B", 1);

        // Этот метод не откатывает транзакцию при StockWarningException
        orderService.processOrderWithPartialErrors(order);

        // Проверяем, что заказ сохранился, несмотря на возможные предупреждения
        assertTrue(orderRepository.existsById(order.getId()));
    }
}

Обратите внимание:

  • Аннотация @Transactional в тестах автоматически откатывает все изменения после каждого теста
  • Это позволяет нам не беспокоиться о очистке базы данных между тестами
  • Мы можем легко проверить, как работает откат при различных сценариях

Типичные ошибки при работе с rollback

Работа с rollback может быть как благом, так и источником проблем, если не соблюдать осторожность:

1. Перехват исключений вручную: Если вы перехватываете исключение внутри транзакционного метода, Spring не узнает об ошибке, и транзакция завершится успешно.

Пример:


@Transactional
public void incorrectTransactionHandling() {
    try {
        saveDataToDatabase();
        throw new RuntimeException("Ошибка");
    } catch (Exception e) {
        // Исключение перехвачено, транзакция не откатится!
    }
}

2. Запуск транзакционного метода через this: Spring использует прокси для обработки транзакций. Если вы вызываете транзакционный метод из другого метода того же класса через this, аннотация @Transactional игнорируется.


public class OrderService {
    @Transactional
    public void methodA() {
        // Транзакция будет работать
    }

    public void methodB() {
        this.methodA(); // Транзакция не будет работать
    }
}

Решение: инжектируйте сервис в самом себе или выносите вызов в другой класс.

3. Операции вне транзакции: если вы меняете данные вне транзакции, они не откатятся. Например, вызов внешнего API или изменение статических переменных остаются за рамками транзакции.


Заключение

Откат транзакций — мощный механизм в Spring, который позволяет обеспечить согласованность данных в случае сбоев или бизнес-ошибок. Но, как и с любым инструментом, откатами нужно пользоваться с умом. Надеюсь, вы больше не будете бояться слова rollback — это не ошибка, это спасательный круг, когда что-то пошло не так.

Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Валерий кривой Уровень 98
4 января 2026
почему такая каша в голове? 🤣