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