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 — це не помилка, це рятівний круг, коли щось пішло не так.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ