我正在尝试为基于Hibernate 5.3和Spring Boot 2.1.3并使用Hibernate二级缓存的应用程序编写测试。
当我执行一批测试时,这些测试正在设置Spring上下文并试图更新一些JPA实体,在某个时候会出现这样的异常:
org.springframework.dao.InvalidDataAccessApiUsageException: Cache[default-update-timestamps-region] is closed; nested exception is java.lang.IllegalStateException: Cache[default-update-timestamps-region] is closed
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:370)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:536)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:533)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy244.save(Unknown Source)
我为Hibernate二级缓存提供了以下配置:
spring。jpa。财产。冬眠隐藏物使用第二级缓存=true spring。jpa。财产。冬眠隐藏物使用_query _cache=true spring。jpa。财产。冬眠隐藏物区域工厂_class=org。冬眠隐藏物jcache。JCacheRegion工厂Spring。jpa。财产。javax。坚持不懈sharedCache。模式=启用_选择性
并使用Hibernate JCache作为依赖项。
据我所知,org。冬眠隐藏物jcache。JCacheRegionFactory
对Spring Test创建的所有上下文重用同一个EhCache CacheManager实例,但在一段时间后,Spring关闭了缓存上下文,这导致关闭CacheManager和缓存。
以前,Hibernate(Hibernate EhCache模块)提供org.hibernate.cache.ehcache.EhCache区域工厂,它每次都会创建新的CacheManager,并且没有上述问题。
有人知道如何为每个Spring测试上下文创建新的CacheManager,并避免使用共享的CacheManager吗?
根本原因在于javax。隐藏物缓存,如果测试在同一个JVM中运行,它保存所有Spring上下文之间共享的CachingProvider
-s的静态集合。
在测试运行期间创建的Spring上下文共享相同的CachingProvider
,因此也共享相同的CacheManagers
。当共享CachingProvider
的任何上下文关闭时,所有相关的缓存管理器也将关闭,从而使引用已关闭的CachingProvider
的剩余Spring上下文处于不一致状态。
为了解决这个问题,每个对CacheManager
的请求都应该返回一个与其他上下文不共享的全新实例。
我编写了一个简单的CachingProvider
实现,它就是这样做的,并且依赖于现有的CachingProvider
。请在下面找到代码。
基类:
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.WeakHashMap;
import javax.cache.CacheManager;
import javax.cache.configuration.OptionalFeature;
import javax.cache.spi.CachingProvider;
/**
* The abstract JCache compatible {@link CachingProvider} suitable for test purposes.
*
* <p>When using JCache and {@link org.hibernate.cache.jcache.JCacheRegionFactory}, {@link CachingProvider}-s
* are shared between Spring contexts, which means that {@link CacheManager}-s are shared too. The class responsible
* for storing loaded {@link CachingProvider}-s is {@link javax.cache.Caching}. If any cached Spring context is closed,
* then all related {@link CacheManager}-s are closed as well, but since these {@link CacheManager}-s are shared with
* remaining Spring contexts, we end up with in an inconsistent state.</p>
*
* <p>The solution is to make sure that each time a {@link CacheManager} for a particular config URI is requested, a new
* instance not shared between Spring contexts is created</p>
*
* <p>The simplest approach is to create a new instance of {@link CachingProvider} for each {@link CacheManager} request
* and manage them separately from {@link CachingProvider}-s loaded via {@link javax.cache.Caching}. This approach
* allows reusing existing required {@link CachingProvider}-s and overcome any sharing issues.</p>
*
* <p>Tests relying on caching functionality MUST make sure that for regular caching the properties
* {@code spring.cache.jcache.provider} and {@code spring.cache.jcache.config} are set and for 2nd-level cache
* the properties {@code spring.jpa.properties.hibernate.javax.cache.provider} and
* {@code spring.jpa.properties.hibernate.javax.cache.uri} are set. Please note that classpath URI-s for
* the {@code spring.jpa.properties.hibernate.javax.cache.uri} property are supported by {@code hibernate-jcache} only
* since 5.4.1, therefore with earlier versions this property should be set programmatically, for example via
* {@link System#setProperty(String, String)}.</p>
*
* @see <a href="https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#caching-provider-jcache-cache-manager">Hibernate
* JCache configuration</a>
* @see org.hibernate.cache.jcache.JCacheRegionFactory
* @see CachingProvider
* @see javax.cache.Caching
*/
public abstract class AbstractTestJCacheCachingProvider implements CachingProvider {
/**
* The {@link CachingProvider}-s specific for a configuration {@link URI} for a specific {@link ClassLoader}.
*
* <p>All access MUST be handled in a <i>synchronized</i> manner.</p>
*/
private final Map<ClassLoader, Map<URI, List<CachingProvider>>>
classLoaderToUriToCachingProviders = new WeakHashMap<>();
/**
* {@inheritDoc}
*/
@Override
public CacheManager getCacheManager(URI uri, ClassLoader classLoader, Properties properties) {
Objects.requireNonNull(uri, "The cache manager configuration URI must not be null.");
Objects.requireNonNull(classLoader, "The class loader must not be null");
final CachingProvider cachingProvider = createCachingProvider();
synchronized (classLoaderToUriToCachingProviders) {
classLoaderToUriToCachingProviders
.computeIfAbsent(classLoader, k -> new HashMap<>())
.computeIfAbsent(uri, k -> new ArrayList<>())
.add(cachingProvider);
}
return cachingProvider.getCacheManager(uri, classLoader, properties);
}
/**
* Creates a {@link CachingProvider}.
*
* @return a created {@link CachingProvider}
*/
protected abstract CachingProvider createCachingProvider();
/**
* {@inheritDoc}
*/
@Override
public ClassLoader getDefaultClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
/**
* {@inheritDoc}
*/
@Override
public URI getDefaultURI() {
throw new UnsupportedOperationException("Please specify an explicit cache manager configuration URI.");
}
/**
* {@inheritDoc}
*/
@Override
public Properties getDefaultProperties() {
return new Properties();
}
/**
* {@inheritDoc}
*/
@Override
public CacheManager getCacheManager(URI uri, ClassLoader classLoader) {
return getCacheManager(uri, classLoader, null);
}
/**
* {@inheritDoc}
*/
@Override
public CacheManager getCacheManager() {
throw new UnsupportedOperationException("The cache manager configuration URI must be specified.");
}
/**
* {@inheritDoc}
*/
@Override
public void close() {
synchronized (classLoaderToUriToCachingProviders) {
classLoaderToUriToCachingProviders.keySet().forEach(this::close);
}
}
/**
* {@inheritDoc}
*/
@Override
public void close(ClassLoader classLoader) {
Objects.requireNonNull(classLoader, "The class loader must not be null");
synchronized (classLoaderToUriToCachingProviders) {
// Process all CachingProvider collections regardless of the configuration URI.
classLoaderToUriToCachingProviders
.getOrDefault(classLoader, Collections.emptyMap())
.values().stream().flatMap(Collection::stream)
// Close all CachingProvider resources since we are sure that CachingProvider-s are not shared
// or reused.
.forEach(CachingProvider::close);
classLoaderToUriToCachingProviders.remove(classLoader);
}
}
/**
* {@inheritDoc}
*/
@Override
public void close(URI uri, ClassLoader classLoader) {
Objects.requireNonNull(uri, "The cache manager configuration URI must not be null");
Objects.requireNonNull(classLoader, "The class loader must not be null");
synchronized (classLoaderToUriToCachingProviders) {
final Map<URI, List<CachingProvider>> uriToCachingProviders = classLoaderToUriToCachingProviders
.getOrDefault(classLoader, Collections.emptyMap());
uriToCachingProviders
.getOrDefault(uri, Collections.emptyList())
// Close all CachingProvider resources since we are sure that CachingProvider-s are not shared
// or reused.
.forEach(CachingProvider::close);
uriToCachingProviders.remove(uri);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSupported(OptionalFeature optionalFeature) {
// Find the first available CachingProvider and delegate the request to it.
synchronized (classLoaderToUriToCachingProviders) {
return classLoaderToUriToCachingProviders.values().stream().findFirst()
.flatMap(uriToCachingProviders -> uriToCachingProviders.values().stream().findFirst())
.flatMap(cachingProviders -> cachingProviders.stream().findFirst())
.map(cachingProvider -> cachingProvider.isSupported(optionalFeature))
.orElse(false);
}
}
}
基于Ehcache的实现:
import javax.cache.spi.CachingProvider;
import org.ehcache.jsr107.EhcacheCachingProvider;
/**
* The test {@link CachingProvider} based on {@link EhcacheCachingProvider}.
*/
public class TestEhcacheJCacheCachingProvider extends AbstractTestJCacheCachingProvider {
@Override
protected CachingProvider createCachingProvider() {
return new EhcacheCachingProvider();
}
}
解决这个问题的一个可能方法是向类中添加如下内容的@DirtiesContext
:
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
public class SomeTestClass {
...
}
这将迫使Spring为此类的所有方法创建一个新的应用程序上下文。就我而言,这解决了问题。
另一种方法是确保Spring知道Hibernate缓存管理器。这可以像本文所描述的那样实现。然而,在某些情况下,这可能是不可能的。
问题内容: 我想在hibernate项目中使用二级缓存,但是我只对hibernate二级缓存了解一点,任何人都可以解释我应该如何在代码中使用它以及需要什么配置和.jar文件吗?我将这些设置设置为我的hibernate.cfg.xml文件 并添加这些jar文件, 我想知道我是否需要更改其他配置? 我怎么知道我的项目使用二级缓存? 如果只是设置此设置,hibernate将自动使用此设置,否则我必须在我
我相信,明智地使用Hibernate的二级缓存可以很好地提高我的应用程序的性能,为此,我已经开始从internet和Hibernate课程学习它。虽然关于二级缓存及其工作方式有很多很好的解释,但我的目标是从我没有找到的具体问题开始,准确地了解事物的工作方式,因此我将问一些关于Hibernate缓存的一般问题,特别是关于二级缓存的问题。 A、 我很乐意回答问题,即使有些问题看起来很明显或无关紧要 >
我使用SpringBoot1.2.5和JPA2来注释实体(并将hibernate作为JPA实现的底层)。 我想在该设置中使用二级缓存,因此实体被注释为 我还在application.properties中添加了以下内容: 在启动过程中,hibernate抱怨缺少,因此我也将此添加到pom中: 但是像这样的查询仍在触发DB查询,而不是使用缓存数据。 你知道少了什么吗?
问题内容: 我正在开发hibernate+ ehcache程序。 ehcache.xml 我在bean.xml中提到了ehcache 我在dao类中的调用方法是 输出为: 但是它在数据库中命中了两次。我在代码中没有发现任何错误。请向我建议为什么它在数据库中命中了两次。 问题答案: 我已经解决了我的问题。我需要添加 在域类中。
当我试图再次运行同样的测试时: 如何禁用缓存,以便gradlew运行整个测试? 谢谢
尝试使用infinispan作为Hibernate的二级缓存,但总是给我以下错误 org.infinispan.jmx.JMX MBean实例类型=CacheManager, name="DefaultCacheManager"已经在'org.infinispan'JMX域下注册。如果您想允许多个配置了相同JMX域的实例,请在org.infinispan.jmx.JmxUtil.buildJmxD