Когда мы говорим о программном управлении транзакциями, мы имеем в виду прямой контроль над транзакциями в коде. В отличие от декларативного подхода, где мы полагаемся на магию 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 для сложных сценариев.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ