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 для складних сценаріїв.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ