java - 从我的二级 ehcache 中检索项目后出现 "org.hibernate.LazyInitializationException"异常

标签 java spring hibernate ehcache lazy-initialization

我将 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 管理的实体不是线程安全的,所以你必须存储和返回分离实体到/从自定义缓存。此外,在分离之前,您需要初始化您希望在实体分离时访问的所有惰性关联。

要实现这一点,您可以:

  1. 使用 EntityManager.detach 显式分离托管实体.您还需要对关联实体进行分离或级联分离操作,并确保适本地处理对来自其他托管实体的分离实体的引用。
  2. 或者,您可以在单独的事务中执行此操作,以确保所有内容都是分离的,并且您不会在当前持久性上下文中从托管实体中引用分离的实体:

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

相关文章:

Java 关闭盒子

java - 无法找到 Face 和 FaceRecognition 类

spring - java.lang.IllegalArgumentException : A ServletContext is required to configure default servlet handling

java - Hibernate 映射同一列两次

java - magick.MagickException : Unable to retrieve handle

java - DSL 处理来自 activeMQ 的回复

spring - 使用 Spring Boot 将日志发送到电子邮件

java - 为什么我们需要apache通用日志 jar 来在Eclipse中安装spring框架

java - Hibernate 实体管理器即使在刷新/刷新后也不会删除对象

java - 组织.hibernate.MappingException : Repeated column in mapping for entity