Getting "org.hibernate.LazyInitializationException" exceptions after retrieving items from my second-level ehcache

Dave picture Dave · Mar 14, 2016 · Viewed 7.8k times · Source

I'm using Hibernate 5.1.0.Final with ehcache and Spring 3.2.11.RELEASE. I have the following @Cacheable annotation set up in one of my DAOs:

@Override
@Cacheable(value = "main")
public Item findItemById(String id)
{
    return entityManager.find(Item.class, id);
}

The item being returned has a number of assocations, some of which are lazy. So for instance, it (eventually) references the field:

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "product_category", joinColumns = { @JoinColumn(name = "PRODUCT_ID") }, inverseJoinColumns = { @JoinColumn(name = "CATEGORY_ID") })
private List<Category> categories;

I notice that within one of my methods that I mark as @Transactional, when the above method is retrieved from the second level cache, I get the below exception when trying to iterate over the categories field:

@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())
    {

The stack trace is:

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)

I understand what the Hibernate session is closed — I do not care about why this happens. Also, it is NOT an option o make the above association eager (instead of lazy). Given that, how can I solve this problem?

Edit: Here is how my ehccahe.xml is configured …

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

and here is how I’m plugging it into my Spring context …

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

Answer

Dragan Bozanovic picture Dragan Bozanovic · Mar 18, 2016

Take a look at a similar question. Basically, your cache is not a Hibernate second-level cache. You are accessing a lazy uninitialized association on a detached entity instance, so a LazyInitializationException is expected to be thrown.

You can try to play around with hibernate.enable_lazy_load_no_trans, but the recommended approach is to configure Hibernate second level cache so that:

  • Cached entities are automatically attached to the subsequent sessions in which they are loaded.
  • Cached data is automatically refreshed/invalidated in the cache when they are changed.
  • Changes to the cached instances are synchronized taking the transaction semantics into consideration. Changes are visible to other sessions/transactions with the desired level of cache/db consistency guarantees.
  • Cached instances are automatically fetched from the cache when they are navigated to from the other entities which have associations with them.

EDIT

If you nevertheless want to use Spring cache for this purpose, or your requirements are such that this is an adequate solution, then keep in mind that Hibernate managed entities are not thread-safe, so you will have to store and return detached entities to/from the custom cache. Also, prior to detachment you would need to initialize all lazy associations that you expect to be accessed on the entity while it is detached.

To achieve this you could:

  1. Explicitly detach the managed entity with EntityManager.detach. You would need to detach or cascade detach operation to the associated entities also, and make sure that the references to the detached entities from other managed entities are handled appropriately.
  2. Or, you could execute this in a separate transaction to make sure that everything is detached and that you don't reference detached entities from the managed ones in the current persistence context:

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

    Because it may happen that the Spring transaction proxy (interceptor) is executed before the cache proxy (both have the same default order value: transaction; cache), then you would always start a nested transaction, be it to really fetch the entity, or to just return the cached instance.

    While we may conclude that performance penalty for starting unneeded nested transactions is small, the issue here is that you leave a small time window when a managed instance is present in the cache.

    To avoid that, you could change the default order values:

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

    so that cache interceptor is always placed before the transaction one.

    Or, to avoid ordering configuration changes, you could simply delegate the call from the @Cacheable method to the @Transactional(propagation = Propagation.REQUIRES_NEW) method on another bean.