Статьи

Кэширование контекста Spring Test + AspectJ @Transactional + Ehcache pain

Вы используете AspectJ @Transactionals и Spring? У вас есть несколько SessionFactory, возможно, одна для встроенной базы данных для модульного тестирования и одна для реальной базы данных для интеграционного тестирования? Вы получаете одно из этих исключений?

org.springframework.transaction.CannotCreateTransactionException: не удалось открыть сеанс Hibernate для транзакции; вложенное исключение — org.hibernate.service.UnknownServiceException: неизвестный сервис запрошен

или же

java.lang.NullPointerException в net.sf.ehcache.Cache.isKeyInCache (Cache.java:3068) в org.hibernate.cache.ehcache.internal.regions.EhcacheDataRegion.contains (EhcacheDataRegion.java:223)

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

Фон

Платформа Spring Text Text context по умолчанию пытается минимизировать количество раз, которое должен запускать контейнер Spring, кэшируя контейнеры. Если вы запускаете несколько тестов, которые все используют одну и ту же конфигурацию, вам нужно будет создать контейнер только один раз для всех тестов, а не создавать его перед каждым тестом. Если у вас есть 1000 тестов, а запуск контейнера занимает 10-15 секунд, это существенно меняет время сборки / тестирования.

Это работает, только если все (вы и все библиотеки, которые вы используете) избегают статических полей (глобальное состояние), и, к сожалению, есть места, где этого трудно / невозможно избежать — даже Spring нарушает это! Несколько мест, которые вызвали у нас проблемы:

  • Spring AspectJ @ Поддержка транзакций
  • EhCache кеш менеджеры

Аспекты являются синглетонами по дизайну. Spring использует это для размещения ссылки на BeanFactory, а также PlatformTransactionManager. Если у вас есть несколько контейнеров, каждый со своим «собственным» AnnotationTransactionAspect, они фактически разделяют AnnotationTransactionAspect, и какой бы контейнер ни запустился последним, он становится «победителем», вызывая всевозможные непредвиденные проблемы, которые трудно отладить.

Ehcache также боль здесь. Библиотека ehcache поддерживает статический список всех менеджеров кеша, которые она создала в ВМ. Поэтому, если вы хотите использовать несколько контейнеров, все они будут иметь ссылку на один и тот же кеш. Spring Test предоставляет механизм для указания того, что этот тест «испортил» контейнер и что его необходимо создать. Это означает уничтожение контейнера после завершения тестового класса. Это нормально, но если в вашем контейнере есть объекты, которые совместно используются другими контейнерами, то уничтожение этого общего объекта разрушает другие контейнеры.

Решения

Самое простое решение — полностью отключить кеширование контекста приложения. Это можно сделать, просто поместив @DirtiesContext в каждом тесте, или (что лучше) вам, вероятно, следует использовать суперклассы («абстрактные тестовые таблицы») для организации ваших тестов, в этом случае просто добавьте @DirtiesContext в базовый класс. К сожалению, вы также потеряете все преимущества кэширования, и время сборки увеличится.

У пружинного контейнера нет общего механизма «самоочищения», потому что такое разделение состояний между контейнерами, безусловно, является антишаблоном. Тот факт, что они сами это делают (AnnotationTransactionAspect, EhCacheManagerFactoryBean.setShared (true) и т. Д.), Указывает на то, что, возможно, им следует добавить некоторую поддержку. Если вы хотите сохранить кеширование, то на шаге 1 убедитесь, что вы не используете в коде синглеты со «статическим полем». Также убедитесь, что любые внешние ресурсы, в которые вы пишете, разделены, чтобы несколько контейнеров могли сосуществовать в одной JVM.

Чтобы решить проблему AspectJ, лучшее решение, которое я нашел, — это создать TestExecutionListener, который «сбрасывает» AnnotationTransactionAspect, чтобы указывать на правильную фабрику бинов и PTM перед выполнением теста. Код для такого слушателя находится в этой сути .

Чтобы затем использовать прослушиватель, вы помещаете @TestListeners в ваше тестовое устройство базового класса, чтобы все тесты запускались с новым прослушивателем. Обратите внимание, что когда вы используете аннотацию @TestListeners, вам необходимо указать все прослушиватели выполнения, включая существующие Spring. В сущности есть пример.

Обходной путь для Ehcache — не допускать совместного использования экземпляров CacheManager между контейнерами. Для этого вы должны убедиться, что все менеджеры кэша имеют уникальные имена. Это на самом деле довольно легко настроить.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@org.springframework.context.annotation.Configuration
public class CacheBeans {
 
    private static final AtomicInteger cacheCounter = new AtomicInteger(0);
     
    @Bean
    public EhCacheManagerFactoryBean ecmfb() {
        EhCacheManagerFactoryBean ecmfb = new EhCacheManagerFactoryBean();
        // cannot share the cache managers
        ecmfb.setShared(false);
        // if you are using ehcache.xml on the classpath then there's nothing more to do than just make it
        // a unique name.  If you are using a different config file then use ecmfb.setConfigLocation()
        ecmfb.setCacheManagerName("ehCache-" + cacheCounter.incrementAndGet());
        return ecmfb;
    }
     
    // more @Bean defs
}

Связанные вопросы

Вот некоторые ссылки на весенние проблемы Jira, освещающие эту проблему

https://jira.spring.io/browse/SPR-6121

https://jira.spring.io/browse/SPR-6353