У попередній лекції ми розібралися з типами виключень у транзакціях і базовими механізмами їх обробки в Spring. Тепер час заглибитись у практику і розглянути більш складні сценарії використання rollback.
Чому важливо правильно налаштовувати rollback?
Неправильне налаштування відкатів може призвести до серйозних проблем:
- Незавершені зміни: частина даних може залишитися в неконсистентному стані, наприклад, при переказі грошей між рахунками.
- Порушення бізнес-правил: система може зберегти зміни, які суперечать бізнес-логіці (скажімо, замовлення з перевищеним лімітом).
- Проблеми масштабування: у багатокористувацьких системах невірна обробка транзакцій призводить до складно відстежуваних помилок.
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,
// замовлення залишиться збереженим
}
}
У цьому прикладі ми бачимо три різні підходи до керування транзакціями:
- Програмний відкат через
setRollbackOnly()— коли нам потрібен повний контроль над процесом - Автоматичний відкат при виключенні — стандартна поведінка Spring для RuntimeException
- Вибірковий відкат за допомогою
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 — це не помилка, це рятівний круг, коли щось пішло не так.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ