Java 获得;org.hibernate.LazyInitializationException“;从我的二级ehcache检索项目后出现异常

Java 获得;org.hibernate.LazyInitializationException“;从我的二级ehcache检索项目后出现异常,java,spring,hibernate,ehcache,lazy-initialization,Java,Spring,Hibernate,Ehcache,Lazy Initialization,我将Hibernate5.1.0.Final与ehcache和Spring3.2.11.RELEASE一起使用。我在我的一个DAOs中设置了以下@Cacheable注释: @Override @Cacheable(value = "main") public Item findItemById(String id) { return entityManager.find(Item.class, id); } 正在返回的项有许多关联,其中一些是惰性的。例如,它(最终)引用字段: @Man

我将Hibernate5.1.0.Final与ehcache和Spring3.2.11.RELEASE一起使用。我在我的一个
DAO
s中设置了以下
@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;
堆栈跟踪是:

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>

看一看。基本上,您的缓存不是Hibernate二级缓存。您正在访问分离实体实例上的惰性未初始化关联,因此预计会引发
LazyInitializationException

您可以尝试使用,但建议的方法是进行配置,以便:

  • 缓存的实体将自动附加到加载它们的后续会话
  • 缓存的数据在更改时会在缓存中自动刷新/失效
  • 在考虑事务语义的情况下,对缓存实例的更改是同步的。更改对具有所需缓存/db级别的其他会话/事务可见
  • 当从与缓存实例关联的其他实体导航到缓存实例时,缓存实例将自动从缓存中获取
编辑

如果您仍然希望使用SpringCache来实现此目的,或者您的需求表明这是一个合适的解决方案,那么请记住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事务代理(拦截器)可能在缓存代理之前执行(两者都具有相同的默认
    顺序
    值:;),因此您总是会启动嵌套事务,无论是真正获取实体,还是仅返回缓存实例

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

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

    <tx:annotation-driven order="200"/>
    <cache:annotation-driven order="100"/>
    
    
    
    所以缓存拦截器总是放在事务拦截器之前

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


  • 问题是您正在缓存对延迟加载的对象的引用。在对象全部加载后缓存该对象,或者根本不使用缓存

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

    Item item = entityManager.find(Item.class, id);
    item.getParent().getProduct().getCategories();
    return item;
    
    还有一个更好的缓存策略是让缓存位于应用程序的服务级别,而不是DAO级别或根本不缓存

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


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

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

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

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

    让我解释一下当您得到
    LazyInitializationException
    时发生了什么:您将一个dao方法标记为
    @org.springframework.cache.annotation.Cacheable
    。在这种情况下会发生以下情况:

  • Spring将拦截器附加到托管bean。拦截器将拦截dao方法调用,它将基于拦截器方法和
    Item item = entityManager.find(Item.class, id);
    item.getParent().getProduct().getCategories();
    return item;
    
    <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" />
    
    <persistence-unit name="default">
        <!-- other configuration lines stripped -->
    
        <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
    
        <!-- other configuration lines stripped -->
    </persistence-unit>
    
    @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;
    
    spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
    
    spring.jpa.open-in-view=true
    
    spring.cache.type=simple