Перейти к основному содержимому

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

· 4 мин. чтения

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, поэтому его должно быть легко импортировать и запускать как есть.