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

I am using Hibernate 5.1.0.Final with ehcache and Spring 3.2.11.RELEASE. I have the following @Cacheable annotation installed in one of my DAO s:

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

The returned element has several associations, some of which are lazy. So, for example, it (in the end) refers to a 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 in one of my methods, which I mark as @Transactional , when the above method is retrieved from the second level cache, I get the following exception when trying to iterate over a category 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()) { 

Stack trace:

 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 that the Hibernate session is closed - I don't care why this happens. Also, this is NOT an option o to make the above association impatient (instead of lazy). Given this, how can I solve this problem?

Edit: This 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 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 this is how I plug 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> 
+8
java spring lazy-initialization hibernate ehcache
source share
3 answers

Take a look at a similar question . Basically, your cache is not a second level cache of Hibernate. You get access to the lazy uninitialized association on a separate instance of the object, so it is expected that there will be a LazyInitializationException .

You can try playing with hibernate.enable_lazy_load_no_trans , but it is recommended to use the second level cache hibernate.enable_lazy_load_no_trans to:

  • Cached objects automatically join subsequent sessions in which they are loaded.
  • Cached data is automatically updated / discarded in the cache when it is changed.
  • Changes in cached instances are synchronized based on the semantics of the transaction. Changes are visible to other sessions / transactions with the required level of cache / db security.
  • Cached instances are automatically retrieved from the cache when they are moved from other objects that have associations with them.

EDIT

If you nevertheless want to use the Spring cache for this purpose or your requirements are such that this is an adequate solution, then keep in mind that Hibernate managed objects are not thread safe, so you will have to store and return individual objects to / from the user cache. In addition, before disconnecting, you will need to initialize all the lazy associations to which you expect to access the object while it is disconnecting.

To do this, you can:

  • Explicitly disable the EntityManager.detach managed entity. You will also need to disable or disable the disconnect operation to related objects and make sure that links to individual objects from other managed objects are handled accordingly.
  • Or you can do this in a separate transaction to make sure everything is disconnected and that you are not referring to individual entities that are managed 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; } 

    Since it may happen that the Spring transaction proxy is executed (executed) before the cache proxy (both have the same default order : transaction ; cache ), then you should always start the nested transaction so that it actually retrieves the object, or simply return the cached instance.

    Although we can conclude that the performance limit for running unnecessary nested transactions is small, the problem is that you leave a small time window when the managed instance is present in the cache.

    To avoid this, you can change the default order values:

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

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

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

+5
source share

What you implemented in your code snippets is a custom cache based on spring-cache. With your implementation, you will need to take care of evicting the cache, making sure that when your graphic objects are cached, they will be loaded correctly, etc. As soon as they receive caching, and the initial hibernation session that loaded them is closed, they will get around, you will no longer be able to navigate through unattached lazy associations. In addition, your custom cache solution in its current state will cache entity diagrams, which is probably not what you want, since any part of this graph may change at a given time, and your cache solution will need to monitor changes in all parts of this schedule handles evictions correctly.

The configuration posted in your question is not a second level cache of Hibernate .

Managing the cache is tricky, and I do not recommend it doing it yourself if you are not sure what you are doing (but then you will not ask this question in Stackoverflow).

Let me explain what happens when you get a LazyInitializationException : you marked one of your dao methods with @org.springframework.cache.annotation.Cacheable . In this case, the following occurs:

  • Spring attaches an interceptor to your managed bean. The interceptor will intercept the call to the dao method, it will create a cache key based on the interceptor method and the actual arguments of the method (this can be configured) and find the cache to see if there is any entry in the cache for this key. If there is a record, it will return this record without actually calling your method. If there is no cache entry for this key, it will call your method, serialize the return value and store it in the cache.
  • If there is no cache entry for the key, your method will be called. Your method uses the provided spring proxy with one proxy for the associated EntityManager stream, which was assigned earlier when spring encountered the first call to the @Transactional method. In your case, it was the getContent(...) method of another spring bean service. So your method loads an object with EntityManager.find() . This will give you a partially loaded object graph containing uninitialized proxies and collections for other related objects not yet loaded by the persistence context.
  • Your method returns with a partially loaded entity graph, and spring immediately serializes it for you and stores it in the cache. Note that serialization of a partially loaded entity graph is deserialized to a partially loaded entity graph.
  • The second time the dao method is called, marked with @Cacheable with the same arguments, spring will detect that there really is a cache entry corresponding to this key and will load and deserialize the record. Your dao method will not be called, since it uses a cached record. Now you are faced with a problem: your deserialized graph of cached objects was partially loaded when stored in the cache, and as soon as you touch any uninitialized part of the graph, you will get a LazyInitializationException . It will always be separated from the deserialized entity, so even if the original EntityManager is still open (which is not the case), you will still get the same exception.

Now the question is: what can you do to avoid a LazyInitializationException . My recommendation is that you forget about implementing a custom cache and simply configure Hibernate to perform caching. I will talk about how to do this later. If you want to stick to the user cache you were trying to implement, here is what you need to do:

Go through your entire code base and find all the calls to the @Cacheable dao method. Follow all possible code paths where the graph of the loaded object is loaded, and mark all parts of the entity graph that are ever affected by client code. Now go back to your @Cacheable method and change it so that it loads and initializes all parts of the entity graph that could ever be affected. Because, as soon as you return it, and it will be serialized, and later deserialized, it will always be in the off state, so itโ€™s better to make sure that all possible paths of the graph are loaded correctly. You should already feel how impractical this will end. If this has not yet convinced you not to follow this direction, here is another argument.

Since you are loading a potentially large fragment of the database, you will have a snapshot of this part of the database at the moment when it is loaded and cached. Now that you are using the cached version of this large fragment of the database, there is a risk that you are using an outdated version of this data. To protect yourself from this, you will need to monitor any changes in the current version of this large fragment of the database that you just cached and evict the entire object graph from the cache. Thus, to a large extent, you need to consider which objects are parts of the graph of your object and configure some event listeners when these entities change and crowd out the entire graph. None of these problems are present in the second-level cache of Hibernate.

Now back to my recommendation: configure the second level cache of Hibernate

Hibernate's Layer 2 cache is managed by Hibernate, and you automatically get sleep eviction control. If you enable Hibernate's second level cache, Hibernate will cache the data needed to restore your objects, and if - when looking for an object to load from the database - it discovers that it has a valid cache entry for your entity, it will skip the database and restoring your essence from the cache. (Note the difference for caching an entity graph with its possible loose associations and uninitialized proxies in your custom cache solution). It will also replace obsolete cache entries when updating an object. It performs all kinds of actions related to cache management, so you do not need to worry about it.

Here you can enable the Hibernate second level cache: in addition to your configuration, do the following:

  • In addition to the sleep mode properties that you already have to manage at the second level, namely

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

    add the following entry:

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

    Alternatively, you can add the shared-cache-mode configuration parameter to your persistence.xml (since you did not publish it, I assumed that you are not using it, therefore, the previous alternative, although this is preferable :)

     <persistence-unit name="default"> <!-- other configuration lines stripped --> <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode> <!-- other configuration lines stripped --> </persistence-unit> 
  • Add javax.persistence.@Cacheable annotation to the @Entity classes you want to cache.
  • If you want to add caching for collection associations that Hibernate does not cache by default, you can add the @org.hibernate.annotations.Cache annotation (with the right concurrency cache strategy selection) for each such collection:

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

For more information, see Performance Improvement / L2 Cache in the Hibernate documentation.

This is a good informative article on the topic: Hibernate L2 Cache Traps / Queries

I put together a small small project based on your published code snippets that you can check to see the second level cache in Hibernate.

+9
source share

The problem is that you are caching references to objects that load lazily. Download the object after loading it or do not use the cache at all.

Here's how you could manually load categories before caching it:

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

In addition, caching should be the best caching strategy at the service level of your application, not from the DAO level or without a cache.

Your problem is caused by the following events:

An item is retrieved without its categories, and then cached in transaction 1. In transaction 2, you call the same method and retrieve the Item and try to read its categories. At this point, hibernate is trying to read the categories from transaction 1, which is associated with the Item object, but transaction 1 has already completed, so it fails.

+3
source share

All Articles