当前位置: 首页 > 知识库问答 >
问题:

从我的二级ehcache检索项目后获得“org.hibernate.LazyLaunalizationException”异常

轩辕亮
2023-03-14

我正在使用Hibernate 5.1.0。最终版本为ehcache和Spring 3.2.11。释放。我在我的一个DAO中设置了以下可缓存注释:

@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的一个方法中,当从二级缓存中检索到上述方法时,我在尝试遍历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())
    {

堆栈跟踪是:

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会话关闭了什么——我不在乎为什么会发生这种情况。此外,这不是让上述关联变得渴望(而不是懒惰)的选项。鉴于此,我如何解决这个问题?

编辑:这是我的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>

共有3个答案

鲁彬炳
2023-03-14

问题是您正在缓存对延迟加载的对象的引用。一旦对象全部加载,就缓存它,或者根本不使用缓存。

以下是如何在缓存类别之前手动加载类别:

Item item = entityManager.find(Item.class, id);
item.getParent().getProduct().getCategories();
return item;

还有一个更好的缓存策略是让缓存位于应用程序的服务级别,而不是DAO级别,或者根本没有缓存。

您的问题是由以下事件引起的:

正在检索没有其类别的项目,然后将其放入事务1中的缓存中。在事务2中,调用相同的方法并检索项目并尝试读取其类别。此时,hibernate尝试从与Item对象关联的事务1中读取类别,但事务1已经完成,因此失败。

祁嘉瑞
2023-03-14

您在代码片段中实现的是基于spring缓存的自定义缓存。在您的实现中,您需要处理缓存逐出,确保在对象图被缓存时,它们被正确加载,等等。一旦它们被缓存并且加载它们的原始hibernate会话被关闭,它们将被分离,您就不能再导航未缓存的懒惰关联。此外,您当前状态下的自定义缓存解决方案将缓存实体图,这可能不是您想要的,因为该图的任何部分都可能在给定的时间发生更改,您的缓存解决方案需要监视该图所有部分的更改,以正确处理逐出。

您在问题中发布的配置不是Hibernate二级缓存。

管理缓存是一项复杂的工作,我不建议您自己去做,除非您完全确定自己在做什么(但这样您就不会在Stackoverflow上问这个问题)。

让我解释一下,当您得到LazyInitializationException时发生了什么:您用org标记了一个dao方法。springframework。隐藏物注释。可缓存。这种情况下会发生以下情况:

  1. Spring将拦截器附加到您的托管bean。拦截器将拦截道方法调用,它将根据拦截器方法和实际方法参数创建一个缓存键(这可以自定义),并查找缓存以查看缓存中是否有该键的任何条目。如果有条目,它将返回该条目,而无需实际调用您的方法。如果该键没有缓存条目,它将调用您的方法,序列化返回值并将其存储在缓存中。
  2. 对于键没有缓存条目的情况,您的方法将被调用。您的方法使用Spring提供的单例代理到线程绑定的EntityManager,它是在Spring遇到第一个@Transactional方法调用时分配的。在您的案例中,这是另一个Spring服务bean的getContent(...)方法。因此,您的方法加载带有EntityManager.find()的实体。这将为您提供一个部分加载的实体图,其中包含尚未由持久性上下文加载的其他关联实体的未初始化代理和集合。
  3. 您的方法返回部分加载的实体图,Spring将立即为您序列化它并将其存储在缓存中。请注意,序列化部分加载的实体图将反序列化为部分加载的实体图。
  4. 在使用相同参数标记为@Cacheable的道方法的第二次调用时,Spring会发现缓存中确实有一个与该键对应的条目,并将加载和反序列化该条目。您的道方法不会被调用,因为它使用缓存的条目。现在您遇到了问题:当您存储在缓存中时,您的反序列化缓存实体图仅被部分加载,一旦您触摸图的任何未初始化部分,您就会得到LazyLaunalizationException。反序列化实体将始终被分离,因此即使原始EntityManager仍然是打开的(不是),您仍然会得到相同的异常。

现在的问题是:如何避免懒散的初始化异常(LazyInitializationException)。好吧,我的建议是,您忘记实现自定义缓存,只需配置Hibernate来为您进行缓存。稍后我将讨论如何做到这一点。如果您想继续使用您尝试实现的自定义缓存,那么需要执行以下操作:

检查整个代码库,找到可缓存dao方法的所有调用。遵循传递加载的实体图的所有可能的代码路径,并标记客户端代码曾经接触过的实体图的所有部分。现在返回到您的可缓存方法,并对其进行修改,以便加载和初始化实体图中可能接触到的所有部分。因为一旦您返回它并将其序列化,然后再反序列化,它将始终处于分离状态,因此最好确保所有可能的图形路径都已正确加载。你应该已经感觉到这最终是多么不切实际。如果这仍然不能说服你不要遵循这个方向,那么这里还有另一个论点。

由于您加载了数据库的一大块,因此在实际加载和缓存该部分数据库时,您将获得该部分数据库的快照。现在,无论何时使用这一大块数据库的缓存版本,都存在使用该数据的旧版本的风险。为了防范这种情况,您需要监视刚刚缓存的数据库的大数据块的当前版本中的任何更改,并从缓存中逐出整个实体图。因此,您非常需要考虑哪些实体是实体图的一部分,并在这些实体发生更改时设置一些事件侦听器,然后逐出整个图。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" />
    

    添加以下条目:

    <entry key="javax.persistence.sharedCache.mode" value="ENABLE_SELECTIVE" />
    

    或者,您可以将共享缓存模式配置选项添加到持久性中。xml(因为您没有发布它,所以我假设您没有使用它,因此使用了前面的替代方法;不过最好使用下面的方法):

    <persistence-unit name="default">
        <!-- other configuration lines stripped -->
    
        <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
    
        <!-- other configuration lines stripped -->
    </persistence-unit>
    

    如果要为Hibernate默认不缓存的集合值关联添加缓存,可以添加一个org。冬眠注释。为每个这样的集合缓存注释(具有适当的缓存并发策略选择):

    @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;
    

    有关更多详细信息,请参阅Hibernate参考文档中的改进性能/二级缓存。

    这是一篇关于该主题的内容丰富的文章:Hibernate二级/查询缓存的陷阱

    我已经根据您发布的代码片段整理了一个小项目,您可以查看Hibernate二级缓存的运行情况。

  • 从阎宝
    2023-03-14

    看看类似的问题。基本上,您的缓存不是Hibernate二级缓存。您正在访问分离实体实例上的惰性未初始化关联,因此预计会抛出LazyLaunalizationException

    您可以尝试使用hibernate.enable_lazy_load_no_trans,但推荐的方法是配置Hibernate二级缓存,以便:

    • 缓存的实体将自动附加到加载它们的后续会话
    • 缓存的数据在更改时会在缓存中自动刷新/失效
    • 考虑到事务语义,对缓存实例的更改是同步的。具有所需级别的缓存/数据库一致性保证的其他会话/事务可以看到更改
    • 当从与缓存实例关联的其他实体导航到缓存实例时,缓存实例会自动从缓存中提取

    编辑

    如果您仍然想为此目的使用Spring缓存,或者您的需求表明这是一个适当的解决方案,那么请记住,Hibernate管理的实体不是线程安全的,因此您必须在自定义缓存中存储和返回分离的实体。此外,在分离之前,您需要初始化实体分离时期望访问的所有惰性关联。

    为此,您可以:

    >

    @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事务代理(拦截器)(两者都有相同的默认顺序:transaction;cache),所以您总是会启动嵌套事务,无论是真正获取实体,还是仅返回缓存实例。

    虽然我们可以得出结论,启动不需要的嵌套事务的性能损失很小,但这里的问题是,当托管实例存在于缓存中时,您会留下一个小时间窗口。

    为了避免这种情况,您可以更改默认订单值:

    <tx:annotation-driven order="200"/>
    <cache:annotation-driven order="100"/>
    

    这样缓存拦截器总是放在事务之前。

    或者,为了避免排序配置更改,您可以简单地将@Cacheable方法的调用委托给另一个bean上的@Transactional(传播=传播。REQUIRES_NEW)方法。

     类似资料:
    • 我目前正在将一些项目转换为maven,并且在战争开始时遇到了一些问题。因此,我刚刚创建了一个非常简单的ehcache maven项目,我得到了一个类加载器类型的问题。这是我的POM: Spring Config:http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring

    • 我有个小问题。我只需要将我的编译android版本从23改为22。当我使用CompileSDK版本23时,一切都很好。一旦我切换到22,它就会产生以下错误。 错误:(2)错误检索项目的父级:没有找到与给定名称'android:文本外观匹配的资源。材料。小部件。按钮。逆'。错误:(2)错误检索项目的父:没有找到与给定名称'android: Widget匹配的资源。材料。按钮。有色'。 我需要解决这个

    • Iam是JPA的新手,尝试创建EJB 3.0+JPA(hibernate)应用程序。当iam将数据持久化到数据库中时,出现以下异常。 sessionBean方法: 客户类(实体类)

    • 本文向大家介绍sharepoint项目。从列表中检索项目,包括了sharepoint项目。从列表中检索项目的使用技巧和注意事项,需要的朋友参考一下 示例