JavaRush /Курсы /Модуль 5. Spring /Программное управление транзакциями: TransactionTemplate

Программное управление транзакциями: TransactionTemplate

Модуль 5. Spring
6 уровень , 4 лекция
Открыта

Когда мы говорим о программном управлении транзакциями, мы имеем в виду прямой контроль над транзакциями в коде. В отличие от декларативного подхода, где мы полагаемся на магию Spring и аннотацию @Transactional, здесь мы сами явно контролируем, что, как и когда происходит с нашей транзакцией.

Давайте разберём основные сценарии, когда программное управление транзакциями имеет смысл:

  1. Гибкость. Иногда у нас есть сложная логика выполнения транзакций, которую сложно реализовать с помощью деклараций.
  2. Частичная транзакция. Нужно выполнить часть операций в рамках транзакции, а некоторые — за её пределами.
  3. Оптимизация. Не требуется выполнение транзакций для каждого метода, и ручной контроль позволяет избежать лишних накладных расходов.

И если это тот самый случай, тогда вам на помощь приходит 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() {
        // Логика второй операции с базой данных
    }
}

В коде:

  1. Инициализация TransactionTemplate. Для работы с TransactionTemplate нам нужен PlatformTransactionManager (например, JpaTransactionManager, если вы работаете с JPA/Hibernate). Мы передаём его в конструктор нашего сервиса и создаём экземпляр TransactionTemplate.
  2. Метод execute. Весь код, который мы хотим выполнить в рамках транзакции, мы оборачиваем в вызов transactionTemplate.execute(). Этот метод принимает в качестве аргумента объект TransactionCallback, реализованный в виде лямбда-функции. Всё, что находится внутри лямбды, выполняется в рамках транзакции.
  3. Откаты транзакции. Если внутри транзакционной логики произойдёт ошибка, 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. Помогает оптимизировать производительность, если транзакция используется только для чтения данных.

Пример: частичная транзакционность

Допустим, вы разрабатываете интернет-магазин и вам нужно:

  1. Создать заказ внутри транзакции.
  2. Отправить 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 в реальном приложении

Давайте добавим программное управление транзакциями в наше приложение. Допустим, у нас есть система управления библиотекой, и нам нужно:

  1. Зарегистрировать нового пользователя.
  2. Одновременно выдать пользователю первую книгу.

Пример:


@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 для сложных сценариев.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ