Dav*_*ave 10 java spring hibernate ehcache lazy-initialization
我正在使用Hibernate 5.1.0.Final和ehcache以及Spring 3.2.11.RELEASE.我@Cacheable在其中一个中设置了以下注释DAO:
@Override
@Cacheable(value = "main")
public Item findItemById(String id)
{
return entityManager.find(Item.class, id);
}
Run Code Online (Sandbox Code Playgroud)
返回的项目有许多关联,其中一些是懒惰的.例如,它(最终)引用该字段:
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "product_category", joinColumns = { @JoinColumn(name = "PRODUCT_ID") }, inverseJoinColumns = { @JoinColumn(name = "CATEGORY_ID") })
private List<Category> categories;
Run Code Online (Sandbox Code Playgroud)
我注意到在我标记的一个方法中@Transactional,当从二级缓存中检索到上述方法时,我在尝试迭代categories字段时得到以下异常:
@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())
{
Run Code Online (Sandbox Code Playgroud)
堆栈跟踪是:
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)
Run Code Online (Sandbox Code Playgroud)
我理解Hibernate会话的关闭 - 我不关心为什么会这样.此外,它不是一个选项o使上述关联渴望(而不是懒惰).鉴于此,我该如何解决这个问题?
编辑:以下是我的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>
Run Code Online (Sandbox Code Playgroud)
这是我如何将它插入我的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>
Run Code Online (Sandbox Code Playgroud)
Dra*_*vic 10
看看类似的问题.基本上,您的缓存不是Hibernate二级缓存.您正在分离的实体实例上访问延迟的未初始化关联,因此LazyInitializationException应该抛出a.
您可以尝试使用hibernate.enable_lazy_load_no_trans,但建议的方法是配置Hibernate二级缓存,以便:
编辑
如果您仍然希望为此目的使用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;
}
Run Code Online (Sandbox Code Playgroud)
因为它可能发生Spring事务代理(拦截)是高速缓存代理之前执行(两者具有相同的默认order值:交易 ; 高速缓存),那么你将总是开始一个嵌套事务,无论是真正获取实体,或只返回缓存的实例.
虽然我们可以得出结论,启动不需要的嵌套事务的性能损失很小,但这里的问题是当缓存中存在托管实例时,您会留下一个小时间窗口.
为避免这种情况,您可以更改默认订单值:
<tx:annotation-driven order="200"/>
<cache:annotation-driven order="100"/>
Run Code Online (Sandbox Code Playgroud)
这样缓存拦截器总是放在事务处理器之前.
或者,为了避免对配置更改进行排序,您可以简单地将@Cacheable方法中的调用委托给@Transactional(propagation = Propagation.REQUIRES_NEW)另一个bean上的方法.
您在代码片段中实现的是基于spring-cache 的自定义缓存.使用您的实现,您需要处理缓存驱逐,确保在您的对象图将被缓存时,它们被正确加载等等.一旦它们被缓存并且加载它们的原始hibernate会话关闭它们将会变得超然,你不能再导航无关联的懒惰联想.此外,您当前状态下的自定义缓存解决方案会缓存实体图形,这可能不是您想要的,因为该图形的任何部分可能在给定时间发生更改,并且您的缓存解决方案需要监视所有部分的更改.该图表正确处理驱逐.
您在问题中发布的配置不是Hibernate二级缓存.
管理缓存是一项复杂的工作,我不建议你自己做,除非你完全确定你在做什么(但是你不会在Stackoverflow上问这个问题).
让我解释一下当你得到的时候发生了什么LazyInitializationException:你用你的方法标记了一个@org.springframework.cache.annotation.Cacheable.在这种情况下会发生以下情况:
EntityManager,这是在Spring遇到第一个@Transactional方法调用时分配的.在你的情况下,这是getContent(...)另一个spring service bean 的方法.所以你的方法加载一个实体EntityManager.find().这将为您提供一个部分加载的实体图,其中包含尚未初始化的代理和集合到尚未由持久性上下文加载的其他关联实体.@Cacheable使用相同参数标记的dao方法的第二次调用中,Spring将发现缓存中确实存在与该键对应的条目,并将加载和反序列化该条目.您的dao方法将不会被调用,因为它使用缓存的条目.现在您遇到了问题:当您存储在缓存中时,您的反序列化缓存实体图仅被部分加载,并且只要触摸图中任何未初始化的部分,您就会得到LazyInitializationException.反序列化的实体将始终被分离,因此即使原始文件EntityManager仍然是打开的(不是),您仍然会得到相同的异常.现在的问题是:你能做些什么来避免这种情况LazyInitializationException.好吧,我的建议是你忘了实现自定义缓存,只是配置Hibernate来为你做缓存.我将在稍后讨论如何做到这一点.如果您想坚持使用您尝试实现的自定义缓存,请执行以下操作:
浏览整个代码库,找到@Cacheabledao方法的所有调用.遵循所加载的实体图传递的所有可能代码路径,并标记实体图的所有部分,这些部分都被客户端代码触及.现在回到您的@Cacheable方法并对其进行修改,以便加载并初始化可能触及的实体图的所有部分.因为一旦你返回并且它被序列化并在以后反序列化,它将始终处于分离状态,因此最好确保正确加载所有可能的图形路径.你应该已经觉得这最终会变得多么不切实际.如果仍然没有说服你不要遵循这个方向,那么这是另一个论点.
由于您加载了一个可能很大的数据库块,因此在实际加载和缓存时,您将在给定时间获得该数据库部分的快照.现在,无论何时使用数据库这一大块的缓存版本,都存在使用该数据的陈旧版本的风险.为了防范这种情况,您需要监视刚刚缓存的数据库的大块当前版本中的任何更改,并从缓存中逐出整个实体图.因此,您几乎需要考虑哪些实体是实体图的一部分,并在这些实体被更改并逐出整个图时设置一些事件侦听器.Hibernate二级缓存不存在这些问题.
现在回到我的建议:设置Hibernate二级缓存
Hibernate二级缓存由Hibernate管理,您可以自动从hibernate进行逐出管理.如果启用了Hibernate二级缓存,Hibernate将缓存重构实体所需的数据,如果 - 当试图从数据库加载实体时 - 它发现它有一个有效的实体缓存条目,它将跳过点击数据库并从其缓存中重建您的实体.(标记差异,以便在自定义缓存解决方案中使用可能未获取的关联和未初始化的代理来缓存实体图).更新实体时,它还将替换过时的缓存条目.它可以处理与管理缓存相关的各种事情,因此您不必担心它.
以下是如何启用Hibernate二级缓存:除了您的配置,请执行以下操作:
除了你已经用于二级管理的hibernate属性,即
<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" />
Run Code Online (Sandbox Code Playgroud)
添加以下条目:
<entry key="javax.persistence.sharedCache.mode" value="ENABLE_SELECTIVE" />
Run Code Online (Sandbox Code Playgroud)
或者,您可以shared-cache-mode为您添加一个配置选项persistence.xml(因为您没有发布它,我假设您不使用它,因此以前的替代方案;但以下是首选):
<persistence-unit name="default">
<!-- other configuration lines stripped -->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<!-- other configuration lines stripped -->
</persistence-unit>
Run Code Online (Sandbox Code Playgroud)javax.persistence.@Cacheable注释添加@Entity到要缓存的类中.如果要为Hibernate默认不缓存的集合值关联添加缓存,可以@org.hibernate.annotations.Cache为每个此类集合添加注释(具有适当的缓存并发策略选择):
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "product_category", joinColumns = { @JoinColumn(name = "PRODUCT_ID")
}, inverseJoinColumns = { @JoinColumn(name = "CATEGORY_ID") })
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<Category> categories;
Run Code Online (Sandbox Code Playgroud)有关更多详细信息,请参阅Hibernate参考文档中的提高性能/二级缓存.
这是一篇关于这个主题的好文章:Hibernate二级/查询缓存的缺陷
我已根据您发布的代码片段组建了一个小项目,您可以查看这些代码片段以查看正在运行的Hibernate二级缓存.
| 归档时间: |
|
| 查看次数: |
6079 次 |
| 最近记录: |