您在使用AspectJ @Transactionals和Spring吗? 您是否有多个SessionFactory,也许一个用于嵌入式数据库进行单元测试,一个用于实际数据库进行集成测试? 您是否遇到这些例外之一?
org.springframework.transaction.CannotCreateTransactionException:无法打开Hibernate Session进行事务处理。 嵌套的异常是org.hibernate.service.UnknownServiceException:请求了未知的服务
要么
net.sf.ehcache.Cache.isKeyInCache(Cache.java:3068)的org.hibernate.cache.ehcache.internal.regions.EhcacheDataRegion.contains(EhcacheDataRegion.java:223)上的java.lang.NullPointerException
然后,您遇到了一个问题,其中多个缓存的应用程序上下文相互踩在一起。 这篇博客文章将描述一些解决我们遇到的问题的策略。
背景
Spring的Text Context框架默认尝试通过缓存容器来最小化spring容器必须启动的次数。 如果您正在运行全部使用相同配置的多个测试,则只需为所有测试创建一次容器,而无需在每次测试之前创建容器。 如果您要进行1000次测试,并且容器需要10到15秒的启动时间,那么构建/测试时间就会大为不同。
仅当每个人(您和您使用的所有库)都避免使用静态字段(全局状态)时,这才有效,不幸的是,在某些情况下,这是很难/不可能避免的,即使spring违反了此规定! 导致我们出现问题的几个地方:
- Spring AspectJ @事务支持
- EhCache缓存管理器
方面是设计上的单例。 Spring使用它来放置对BeanFactory和PlatformTransactionManager的引用。 如果您有多个带有各自“自己的” AnnotationTransactionAspect的容器,则它们实际上共享AnnotationTransactionAspect,而最后启动的容器是“赢家”,从而导致各种意外的难以调试的问题。
Ehcache在这里也很痛苦。 ehcache库维护它在VM中创建的所有缓存管理器的静态列表。 因此,如果要使用多个容器,它们将共享对同一缓存的引用。 Spring Test提供了一种机制来指示该测试已“污染”了容器并需要创建它。 这意味着在完成测试类后会破坏容器。 很好,但是如果您的容器具有其他容器共享的对象,则销毁该共享对象会破坏其他容器。
解决方案
最简单的解决方案是基本上完全禁用应用程序上下文缓存。 只需在每个测试上放置@DirtiesContext即可完成此操作,或者(最好)您可能应该使用超级类(“抽象测试夹具”)来组织您的测试,在这种情况下,只需在基类上添加@DirtiesContext。 不幸的是,您还失去了所有缓存优势,并且构建时间将增加。
弹簧容器没有“清理自身”的通用机制,因为跨容器共享状态肯定是一种反模式。 他们自己这样做(AnnotationTransactionAspect,EhCacheManagerFactoryBean.setShared(true)等),这表明他们可能应该添加一些支持。 如果要继续缓存,则第1步是确保您的代码中不使用任何“静态字段”单例。 还要确保将要写入的所有外部资源分开,以便多个容器可以共存于同一JVM中。
为了解决AspectJ问题,我发现的最佳解决方案是创建一个TestExecutionListener,以在测试执行之前“重置” AnnotationTransactionAspect以指向正确的bean工厂和PTM。 这种侦听器的代码在本要点中 。
然后,要使用侦听器,请将@TestListeners放在基类测试夹具上,以便所有测试都使用新的侦听器运行。 请注意,使用@TestListeners批注时,必须指定所有执行侦听器,包括现有的Spring侦听器。 要点有一个例子。
Ehcache的解决方法是不允许在容器之间共享CacheManager实例。 为此,您必须确保所有缓存管理器都有唯一的名称。 实际上,这很容易配置。
@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
}
相关问题
以下是一些涉及此问题的Spring jira问题的链接:
https://jira.spring.io/browse/SPR-6121
https://jira.spring.io/browse/SPR-6353
翻译自: https://www.javacodegeeks.com/2014/04/spring-test-context-caching-aspectj-transactional-ehcache-pain.html