我将 Hibernate 5.1.0.Final 与 ehcache 和 Spring 3.2.11.RELEASE 一起使用。我在我的一个 DAO
中设置了以下 @Cacheable
注释:
@Override
@Cacheable(value = "main")
public Item findItemById(String id)
{
return entityManager.find(Item.class, id);
}
被返回的项目有许多关联,其中一些是惰性的。因此,例如,它(最终)引用了该字段:
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "product_category", joinColumns = { @JoinColumn(name = "PRODUCT_ID") }, inverseJoinColumns = { @JoinColumn(name = "CATEGORY_ID") })
private List<Category> categories;
我注意到在我标记为 @Transactional
的方法之一中,当从二级缓存中检索到上述方法时,在尝试遍历类别字段时出现以下异常:
@Transactional(readOnly=true)
public UserContentDto getContent(String itemId, String pageNumber) throws IOException
{
Item Item = contentDao.findItemById(ItemId);
…
// Below line causes a “LazyInitializationException” exception
for (Category category : item.getParent().getProduct().getCategories())
{
堆栈跟踪是:
16:29:42,557 INFO [org.directwebremoting.log.accessLog] (ajp-/127.0.0.1:8009-18) Method execution failed: : org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.mainco.subco.ecom.domain.Product.standardCategories, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:579) [hibernate-myproject-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:203) [hibernate-myproject-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:558) [hibernate-myproject-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:131) [hibernate-myproject-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:277) [hibernate-myproject-5.1.0.Final.jar:5.1.0.Final]
at org.mainco.subco.ebook.service.ContentServiceImpl.getCorrelationsByItem(ContentServiceImpl.java:957) [myproject-90.0.0-SNAPSHOT.jar:]
at org.mainco.subco.ebook.service.ContentServiceImpl.getContent(ContentServiceImpl.java:501) [myproject-90.0.0-SNAPSHOT.jar:]
at sun.reflect.GeneratedMethodAccessor819.invoke(Unknown Source) [:1.6.0_65]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) [rt.jar:1.6.0_65]
at java.lang.reflect.Method.invoke(Method.java:597) [rt.jar:1.6.0_65]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) [spring-aop-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183) [spring-aop-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) [spring-aop-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96) [spring-tx-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260) [spring-tx-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94) [spring-tx-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) [spring-aop-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91) [spring-aop-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) [spring-aop-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204) [spring-aop-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at com.sun.proxy.$Proxy126.getContent(Unknown Source)
我理解 Hibernate session 关闭的原因——我不关心为什么会这样。此外,使上述关联变得急切(而不是懒惰)也不是一个选项。鉴于此,我该如何解决这个问题?
编辑:这是我的 ehccahe.xml 的配置方式......
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd" updateCheck="false">
<!-- This is a default configuration for 256Mb of cached data using the JVM's heap, but it must be adjusted
according to specific requirement and heap sizes -->
<defaultCache maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="86400"
timeToLiveSeconds="86400"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
<cache name="main" maxElementsInMemory="10000" />
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1,
multicastGroupPort=4446, timeToLive=32"/>
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=localhost, port=40001,
socketTimeoutMillis=2000"/>
</ehcache>
下面是我如何将它插入到我的 Spring 上下文中......
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="packagesToScan" value="org.mainco.subco" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
</property>
<property name="dataSource" ref="dataSource"/>
<property name="jpaPropertyMap" ref="jpaPropertyMap" />
</bean>
<cache:annotation-driven key-generator="cacheKeyGenerator" />
<bean id="cacheKeyGenerator" class="org.mainco.subco.myproject.util.CacheKeyGenerator" />
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheCacheManager"
p:cacheManager-ref="ehcache"/>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:configLocation="classpath:ehcache.xml"
p:shared="true" />
<util:map id="jpaPropertyMap">
<entry key="hibernate.show_sql" value="false" />
<entry key="hibernate.hbm2ddl.auto" value="validate"/>
<entry key="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
<entry key="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup" />
<entry key="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
<entry key="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider"/>
<entry key="hibernate.cache.use_second_level_cache" value="true" />
<entry key="hibernate.cache.use_query_cache" value="false" />
<entry key="hibernate.generate_statistics" value="false" />
</util:map>
<bean id="entityManager" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
最佳答案
看看similar question .基本上,您的缓存不是 Hibernate 二级缓存。您正在访问分离实体实例上的延迟未初始化关联,因此预计会抛出 LazyInitializationException
。
你可以试试 hibernate.enable_lazy_load_no_trans
, 但推荐的方法是配置 Hibernate second level cache这样:
- 缓存的实体会自动附加到加载它们的后续 session 。
- 缓存的数据在更改时会在缓存中自动刷新/失效。
- 对缓存实例的更改是同步的,同时考虑到事务语义。具有所需缓存/数据库级别的其他 session /事务可以看到更改 consistency guarantees .
- 当从与其有关联的其他实体导航到缓存实例时,缓存实例会自动从缓存中获取。
编辑
如果你仍然想为此目的使用 Spring 缓存,或者你的要求是这是一个足够的解决方案,那么请记住 Hibernate 管理的实体不是线程安全的,所以你必须存储和返回分离实体到/从自定义缓存。此外,在分离之前,您需要初始化您希望在实体分离时访问的所有惰性关联。
要实现这一点,您可以:
- 使用
EntityManager.detach
显式分离托管实体.您还需要对关联实体进行分离或级联分离操作,并确保适本地处理对来自其他托管实体的分离实体的引用。 或者,您可以在单独的事务中执行此操作,以确保所有内容都是分离的,并且您不会在当前持久性上下文中从托管实体中引用分离的实体:
@Override @Cacheable(value = "main") @Transactional(propagation = Propagation.REQUIRES_NEW) public Item findItemById(String id) { Item result = entityManager.find(Item.class, id); Hibernate.initialize(result.getAssociation1()); Hibernate.initialize(result.getAssociation2()); return result; }
因为有可能发生 Spring 事务代理(拦截器)先于缓存代理执行(两者具有相同的默认
order
值:transaction ; cache ),那么你总是启动嵌套事务,无论是真正获取实体,还是只返回缓存的实例。虽然我们可以得出结论,启动不需要的嵌套事务的性能损失很小,但这里的问题是当缓存中存在托管实例时,您会留下一个小时间窗口。
为避免这种情况,您可以更改默认订单值:
<tx:annotation-driven order="200"/> <cache:annotation-driven order="100"/>
这样缓存拦截器总是放在事务拦截器之前。
或者,为了避免对配置更改进行排序,您可以简单地将
@Cacheable
方法的调用委托(delegate)给另一个上的@Transactional(propagation = Propagation.REQUIRES_NEW)
方法 bean 。
关于java - 从我的二级 ehcache 中检索项目后出现 "org.hibernate.LazyInitializationException"异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35997541/