«1. Введение

Spring имеет отличную поддержку декларативного управления транзакциями в коде приложения, а также в интеграционных тестах.

Однако иногда нам может понадобиться детальный контроль над границами транзакций.

В этой статье мы увидим, как программно взаимодействовать с автоматическими транзакциями, настроенными Spring, в транзакционных тестах.

2. Предпосылки

Предположим, что у нас есть несколько интеграционных тестов в нашем приложении Spring.

В частности, мы рассматриваем тесты, которые взаимодействуют с базой данных, например, проверяют, правильно ли ведет себя наш уровень сохраняемости.

Давайте рассмотрим стандартный тестовый класс, аннотированный как транзакционный:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { HibernateConf.class })
@Transactional
public class HibernateBootstrapIntegrationTest { ... }

В таком тесте каждый тестовый метод заключен в транзакцию, которая откатывается при выходе из метода.

Конечно, можно аннотировать только определенные методы. Все, что мы обсудим в этой статье, относится и к этому сценарию.

3. Класс TestTransaction

Оставшуюся часть статьи мы посвятим обсуждению одного класса: org.springframework.test.context.transaction.TestTransaction.

Это служебный класс с несколькими статическими методами, которые мы можем использовать для взаимодействия с транзакциями в наших тестах.

Каждый метод взаимодействует с единственной текущей транзакцией, которая выполняется во время выполнения тестового метода.

3.1. Проверка состояния текущей транзакции

Одна вещь, которую мы часто делаем в тестах, — это проверка того, что вещи находятся в том состоянии, в котором они должны быть.

Следовательно, мы можем захотеть проверить, есть ли текущая активная транзакция:

assertTrue(TestTransaction.isActive());

Или нам может быть интересно проверить, помечена ли текущая транзакция для отката или нет:

assertTrue(TestTransaction.isFlaggedForRollback());

Если это так, то Spring откатит его непосредственно перед его завершением либо автоматически, либо программно. В противном случае он зафиксирует его непосредственно перед закрытием.

3.2. Пометка транзакции для фиксации или отката

Мы можем программно изменить политику фиксации или отката транзакции перед ее закрытием:

TestTransaction.flagForCommit();
TestTransaction.flagForRollback();

Обычно транзакции в тестах помечаются для отката при запуске. Однако, если у метода есть аннотация @Commit, вместо этого они начинают помечаться для фиксации:

@Test
@Commit
public void testFlagForCommit() {
    assertFalse(TestTransaction.isFlaggedForRollback());
}

Обратите внимание, что эти методы просто помечают транзакцию, как следует из их названий. То есть транзакция не фиксируется и не откатывается немедленно, а только непосредственно перед ее завершением.

3.3. Запуск и завершение транзакции

Чтобы зафиксировать или откатить транзакцию, мы либо позволяем методу выйти, либо явно завершаем его:

TestTransaction.end();

Если позже мы хотим снова взаимодействовать с базой данных, мы должны начать новую транзакцию:

TestTransaction.start();

Обратите внимание, что новая транзакция будет помечена для отката (или фиксации) в соответствии со значением метода по умолчанию. Другими словами, предыдущие вызовы flagFor… не оказывают никакого влияния на новые транзакции.

4. Некоторые детали реализации

В TestTransaction нет ничего волшебного. Теперь мы рассмотрим его реализацию, чтобы узнать немного больше о транзакциях в тестах Spring.

Мы видим, что его несколько методов просто получают доступ к текущей транзакции и инкапсулируют некоторые ее функции.

4.1. Откуда TestTransaction получает текущую транзакцию?

Давайте сразу перейдем к коду:

TransactionContext transactionContext
  = TransactionContextHolder.getCurrentTransactionContext();

TransactionContextHolder — это просто статическая оболочка вокруг ThreadLocal, содержащего TransactionContext.

4.2. Кто устанавливает локальный контекст потока?

Если мы посмотрим, кто вызывает метод setCurrentTransactionContext, мы обнаружим, что есть только один вызывающий объект: TransactionalTestExecutionListener.beforeTestMethod.

TransactionalTestExecutionListener — это прослушиватель, который Springs автоматически настраивает для тестов с аннотацией @Transactional.

Обратите внимание, что TransactionContext не содержит ссылки на какую-либо реальную транзакцию; вместо этого это просто фасад над PlatformTransactionManager.

Да, этот код многоуровневый и абстрактный. Таковы, как правило, основные части среды Spring.

«Интересно видеть, что при такой сложности Spring не творит никакой черной магии — просто много необходимой бухгалтерии, сантехники, обработки исключений и так далее.

5. Выводы

В этом кратком руководстве мы увидели, как программно взаимодействовать с транзакциями в тестах на основе Spring.

Реализацию всех этих примеров можно найти в проекте GitHub — это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.