Уяви, що доводилося б вручну відкривати й закривати транзакції щоразу, щойно в коді щось йде не так. Це приблизно як вручну сортувати величезні масиви замість того, щоб користуватися вбудованими методами Java. Можна, але навіщо морочитися?
Декларативне управління транзакціями дозволяє нам зовсім відмовитись від явного коду управління транзакціями (напр., beginTransaction і commit) і зосередитись на логіці самої аплікації. Spring бере на себе всю рутину — створення, управління і відкат транзакцій.
Переваги декларативного підходу
- Підвищена читабельність коду: код аплікації стає чистішим, бо не потрібно явно прописувати логіку управління транзакціями.
- Менше багів: ризик забути закрити транзакцію мінімальний, адже цим керує Spring.
- Більше часу на важливе: замість боротьби з транзакціями ти можеш зосередитись на бізнес-логіці (або на піці — вирішувати тобі).
Декларативне управління транзакціями в Spring
Тепер подивимось, як Spring забезпечує декларативне управління транзакціями через анотації. За кулісами працює AOP (аспектно-орієнтоване програмування). Spring створює проксі-об'єкти твоїх компонентів і додає логіку управління транзакціями в точки входу методів. Цей підхід називається "декларативним", бо ми декларуємо параметри транзакції, а Spring робить усе інше.
Як працює транзакція через @Transactional?
- Коли метод, позначений анотацією
@Transactional, викликається, створюється проксі (замісник), який керує транзакцією. - Проксі перехоплює виклик методу, відкриває транзакцію і гарантує, що у випадку виключення транзакція буде відкотена.
- Якщо метод успішно завершується, транзакція фіксується (committed).
Приблизно так виглядає під капотом взаємодія:
Метод викликається -> Проксі перевіряє @Transactional -> Відкривається транзакція ->
-> Виконується метод -> Успіх? commit() : rollback()
Налаштування декларативного управління
1. Підключення керування транзакціями
Щоб увімкнути транзакції в проєкті потрібно зробити всього кілька кроків:
- Додай залежність на
spring-txу свійpom.xmlабоbuild.gradle(якщо ти використовуєш Spring Boot, вона вже там):<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency> - Увімкни підтримку анотації
@Transactionalв аплікації. Для цього додай у конфігураційний клас анотацію@EnableTransactionManagement:@Configuration @EnableTransactionManagement public class AppConfig { // Тут можна налаштувати bean-и твоєї аплікації }
2. Використання @Transactional в методах
Після налаштування можна помічати методи анотацією @Transactional і керувати там транзакціями.
Ось приклад:
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// Крок 1: Збереження замовлення
orderRepository.save(order);
// Крок 2: Оновлення балансу користувача
userAccountService.decreaseBalance(order.getUserId(), order.getTotalPrice());
// Навмисно створимо помилку (наприклад, ділення на нуль)
int error = 1 / 0;
// Якби не @Transactional, зміни могли б зберегтися в базі,
// але транзакція автоматично відкотиться через виключення.
}
}
Тут ми навмисно ламаємо код, ділячи на нуль. Але завдяки анотації @Transactional все відкотиться! Користувач не втратить гроші, а база залишиться в цілості.
3. Гнучкість налаштувань @Transactional
Параметри анотації: @Transactional дозволяє вказати багато параметрів:
propagation: як транзакція має поводитися в контексті "взаємних викликів методів".isolation: рівень ізоляції транзакції (наприклад,READ_COMMITTED,SERIALIZABLE).rollbackFor: виключення, при яких має виконуватися відкат.timeout: таймаут транзакції.readOnly: якщо транзакція тільки читає дані, ставимоtrue.
Приклад налаштування параметрів:
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.SERIALIZABLE,
timeout = 5,
rollbackFor = {SQLException.class, CustomException.class},
readOnly = false
)
public void processOrder(Order order) {
// Твій код
}
Приклад з propagation
Spring підтримує кілька типів поведінки транзакцій. Наприклад:
REQUIRED: використати існуючу транзакцію або створити нову.REQUIRES_NEW: завжди створювати нову транзакцію.NESTED: вкладені транзакції.
Приклад різної поведінки:
@Service
public class PaymentService {
@Transactional(propagation = Propagation.REQUIRED)
public void processPayment() {
// Код для обробки платежу
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logPayment() {
// Код для логування транзакції
}
}
Як Spring реалізує декларативне управління? (Під капотом)
Spring використовує AOP і створює проксі-об'єкт. Це означає, що коли ти викликаєш метод з анотацією @Transactional, реальний виклик проходить через проксі, який додає управління транзакцією. Наприклад, метод загортається в щось на кшталт:
transactionManager.begin();
try {
method(); // Виклик реального методу
transactionManager.commit();
} catch (Exception e) {
transactionManager.rollback();
}
Ця магія дозволяє нам уникнути ручного керування транзакціями.
Практичний приклад: управління транзакціями в e-commerce аплікації
Давай створимо простий e-commerce сценарій, де користувач оформляє замовлення. Ми будемо використовувати @Transactional для управління всіма операціями: збереження замовлення, оновлення балансу користувача.
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private UserAccountService userAccountService;
@Transactional
public void placeOrder(Order order) {
// Зберігаємо замовлення
orderRepository.save(order);
// Оновлюємо баланс користувача
userAccountService.decreaseBalance(order.getUserId(), order.getTotalPrice());
// Навмисне створення помилки
if (order.getTotalPrice() > 10000) {
throw new RuntimeException("Бюджет перевищено!");
}
}
}
При перевищенні бюджету транзакція відкотиться. Ми залишаємося з узгодженими даними і задоволеним користувачем.
Перевіряємо відкат вручну
Можеш написати тест, щоб перевірити, що дані відкотяться при виникненні виключення:
@SpringBootTest
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Autowired
private OrderRepository orderRepository;
@Test
public void testTransactionRollback() {
Order order = new Order(/* дані замовлення */);
try {
orderService.placeOrder(order);
} catch (RuntimeException e) {
// Ловимо RuntimeException
}
// Переконуємося, що замовлення НЕ збережено в базі
assertEquals(0, orderRepository.count());
}
}
Типові помилки і підводні камені
- Не працює
@Transactional?- Перевір, чи ввімкнув ти
@EnableTransactionManagement. Без цього анотація працювати не буде. - Переконайся, що метод з
@Transactionalвикликається через Spring Bean, інакше проксі не буде використовуватись.
- Перевір, чи ввімкнув ти
- Не відкотиться транзакція?
- Spring за замовчуванням відкатує лише
RuntimeException. Якщо хочеш обробляти інші виключення, використовуй параметрrollbackFor.
- Spring за замовчуванням відкатує лише
- Вкладені транзакції можуть заплутати:
- Пам'ятай, що
propagation = REQUIRES_NEWстворює нову транзакцію, тоді якREQUIREDпродовжує поточну.
- Пам'ятай, що
На цьому етапі ти готовий керувати транзакціями за допомогою анотації @Transactional. Хай твої дані завжди будуть узгоджені, а системи — відмовостійкими!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ