Коли ми говоримо про програмне керування транзакціями, маємо на увазі прямий контроль за транзакціями в коді. На відміну від декларативного підходу, де ми покладаємося на магію Spring і анотацію @Transactional, тут ми самі явно контролюємо, що, як і коли відбувається з нашою транзакцією.
Давай розберемо основні сценарії, коли програмне керування транзакціями має сенс:
- Гнучкість. Іноді в нас є складна логіка виконання транзакцій, яку важко реалізувати за допомогою декларацій.
- Часткова транзакція. Потрібно виконати частину операцій в межах транзакції, а деякі — за її межами.
- Оптимізація. Не треба виконувати транзакції для кожного методу, і ручний контроль дозволяє уникнути зайвих накладних витрат.
І якщо це той випадок, тоді тобі на допомогу приходить TransactionTemplate.
Що таке TransactionTemplate?
TransactionTemplate — це клас Spring, який спрощує програмне керування транзакціями. Він надає API, який дозволяє обгортати бізнес-логіку в транзакції, не змушуючи нас опускатися до рівня керування низькорівневими API (наприклад, Connection або TransactionManager).
Ось головні плюси TransactionTemplate:
- Простота використання: не треба вручну відкривати/закривати транзакції.
- Мінімізація помилок: Spring бере на себе інфраструктурні задачі, залишаючи розробнику тільки бізнес-логіку.
- Легка налаштування: можна визначати правила транзакції (наприклад, ізоляцію, тайм-аути і т.д.) прямо в коді.
Як використовувати TransactionTemplate
Перш ніж ми зануримось у деталі, давай розглянемо приклад:
@Component
public class TransactionalService {
private final TransactionTemplate transactionTemplate;
public TransactionalService(PlatformTransactionManager transactionManager) {
// Ініціалізуємо TransactionTemplate за допомогою TransactionManager
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public void executeTransactionalLogic() {
transactionTemplate.execute(status -> {
// У цій транзакції виконуємо бізнес-логіку
performDatabaseOperation1();
performDatabaseOperation2();
// Якщо потрібно, можна викликати rollback вручну
// status.setRollbackOnly();
return null; // Повертаємо результат (можна повернути будь-який об'єкт або null)
});
}
private void performDatabaseOperation1() {
// Логіка першої операції з базою даних
}
private void performDatabaseOperation2() {
// Логіка другої операції з базою даних
}
}
У коді:
- Ініціалізація
TransactionTemplate. Для роботи зTransactionTemplateнам потрібенPlatformTransactionManager(наприклад,JpaTransactionManager, якщо ти працюєш з JPA/Hibernate). Ми передаємо його в конструктор нашого сервісу і створюємо екземплярTransactionTemplate. - Метод
execute. Весь код, який ми хочемо виконати в межах транзакції, ми обгортаємо в викликtransactionTemplate.execute(). Цей метод приймає як аргумент об'єктTransactionCallback, реалізований у вигляді лямбда-функції. Все, що знаходиться всередині лямбди, виконується в межах транзакції. - Відкати транзакції. Якщо всередині транзакційної логіки станеться помилка, Spring автоматично викличе rollback. Якщо ми самі хочемо примусово відкотити транзакцію, можна викликати
status.setRollbackOnly().
Налаштування TransactionTemplate
Ізоляція, тайм-аути і readOnly
Так само, як і з анотацією @Transactional, ми можемо задавати налаштування транзакції через TransactionTemplate. Приклад:
@Transactional
public class ConfigurableTransactionalService {
private final TransactionTemplate transactionTemplate;
public ConfigurableTransactionalService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
// Встановлюємо ізоляцію
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
// Тайм-аут транзакції (в секундах)
this.transactionTemplate.setTimeout(5);
// Транзакція тільки для читання
this.transactionTemplate.setReadOnly(true);
}
public void executeReadOnlyTransaction() {
transactionTemplate.execute(status -> {
performReadOperation();
return null;
});
}
private void performReadOperation() {
// Транзакція для операцій читання
}
}
Деталі:
setIsolationLevel. Налаштовує рівень ізоляції для транзакції (наприклад,ISOLATION_READ_COMMITTED,ISOLATION_SERIALIZABLEі т.д.). Це важливо для керування конкурентними запитами.setTimeout. Визначає, скільки транзакція може виконуватися до автоматичного відкату.setReadOnly. Допомагає оптимізувати продуктивність, якщо транзакція використовується тільки для читання даних.
Приклад: часткова транзакційність
Припустимо, ти розробляєш інтернет-магазин і потрібно:
- Створити замовлення всередині транзакції.
- Відправити email-сповіщення користувачу поза транзакцією (щоб довгий процес не блокував базу даних).
Ось як це можна реалізувати за допомогою TransactionTemplate:
@Component
public class OrderService {
private final TransactionTemplate transactionTemplate;
private final EmailService emailService;
public OrderService(PlatformTransactionManager transactionManager, EmailService emailService) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.emailService = emailService;
}
public void placeOrder(Order order) {
transactionTemplate.execute(status -> {
// Операції з базою даних всередині транзакції
saveOrder(order);
updateInventory(order);
return null;
});
// Операції поза транзакцією
emailService.sendOrderConfirmation(order.getEmail());
}
private void saveOrder(Order order) {
// Логіка збереження замовлення
}
private void updateInventory(Order order) {
// Логіка оновлення інвентарю
}
}
Проблеми та помилки при використанні TransactionTemplate
Помилка: виклик rollback не відбувається. Якщо всередині транзакційного коду відбувається виключення, переконайся, що це виключення є unchecked (наступником RuntimeException). Spring за замовчуванням не відкатує транзакції для checked виключень. Це можна налаштувати за допомогою анотації @Transactional, вказавши rollbackFor, або вручну викликати status.setRollbackOnly().
Помилка: read-only транзакція змінює дані. Якщо ти встановив setReadOnly(true), а потім здійснив запис у базу, Spring може не відкотити зміни (залежить від використовуваної бази даних). Використовуй readOnly тільки для явно "читаючих" транзакцій.
Практика: використання TransactionTemplate у реальному застосунку
Давай додамо програмне керування транзакціями в наш застосунок. Припустимо, у нас є система керування бібліотекою, і потрібно:
- Зареєструвати нового користувача.
- Одночасно видати користувачу першу книгу.
Приклад:
@Service
public class LibraryService {
private final TransactionTemplate transactionTemplate;
private final UserRepository userRepository;
private final BookRepository bookRepository;
public LibraryService(PlatformTransactionManager transactionManager,
UserRepository userRepository,
BookRepository bookRepository) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.userRepository = userRepository;
this.bookRepository = bookRepository;
}
public void registerUserAndIssueBook(User user, Book book) {
transactionTemplate.execute(status -> {
userRepository.save(user); // Зберігаємо користувача
book.setIssuedTo(user); // Оновлюємо статус книги
bookRepository.save(book); // Зберігаємо зміни книги
return null;
});
}
}
Коли використовувати TransactionTemplate?
Отже, коли варто обрати TransactionTemplate замість @Transactional?
- Тобі потрібен більший контроль над транзакцією — виконання частини логіки поза транзакцією.
- Ти пишеш бібліотеку або фреймворк і хочеш зробити логіку керування транзакціями універсальною.
- Твої транзакції складні і вимагають ручного налаштування (наприклад, частковий відкат).
Для більшості випадків вистачає декларативного керування через @Transactional, але завжди приємно мати в запасі інструмент на кшталт TransactionTemplate для складних сценаріїв.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ