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