Программные транзакции в Spring TestContext Framework

Программные транзакции в Spring TestContext Framework

1. Вступление

Spring имеет отличную поддержку декларативного управления транзакциями как вapplication code, так и вintegration tests.

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

В этой статье мы увидимhow to programmatically interact with automatic transactions set up by Spring in transactional tests.

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

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

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

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

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

В таком тестеevery test method is wrapped in a transaction, which gets rolled back when the method exits.

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

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());
}

Обратите внимание, что эти методы просто помечают транзакцию, как и подразумевают их имена. То естьthe transaction isn’t committed or rolled back immediately, but only just before it ends.

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.

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