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

4

12

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>
Bromal answered 14/3, 2016 at 20:35 Comment(15)
Hibernate 5.1.0.Final with ehcache and Spring 3.2.11.RELEASE? Spring 3.x does not support hibernate 5.x. You can use them together but do not expect great results. For starters, upgrade to Spring 4.2.x release, of course if you have this optionRosalie
So you're answer is that this isn't working because I'm not using hte right version of Spring?Bromal
Maybe after upgrade it continues to not working but with your current combination you may see other anomalies in futureRosalie
I upgraded. Got the same error.Bromal
#26531805Rosalie
I see a lot of explanations in the unaccepted answer, but there is no offering of a solution. Calling "merge" on all the returned entities is not an option.Bromal
#21574736 AND #14542640 AND javarevisited.blogspot.com/2014/04/…Inept
ignaciosuay.com/…Inept
IF you think one of these consittutes an answer, go ahead and answer and you can earn yourself a 250 point bounty.Bromal
I'll be completely honest, I stopped reading right after the first code snippet. NEVER put managed entities within your cache! Except if you can guarantee that these are detached prior insertion. When you put anything within a cache (certainly ehcache), it could be accessed concurrently by multiple thread. There is no safe way to do that on a managed entity.Contrary
Does it work if you disable second level caching?Agley
@AlexSnaps, "detached prior insertion" ... I'm not understanding you. I'm looking for a way to cleanly cache the results of these methods and I'm hemmed in by Spring and Hibernate. If you have a solution that fits that structure, by all means, lay it down brother!Bromal
See docs.oracle.com/cd/E14101_01/doc.1013/e13981/…Contrary
@AlexSnaps, I used to think that caching entities (managed of course, since it operates without any code change) was the purpose of L2 cache. Is is not?Evin
@PlínioPantaleão Not sure what you mean, but the L2 cache never caches plain entities, it stores dehydrated version of these, that it then re-assembles to an entity for you Session (L1 Cache) to use. Long story short: never put a managed entity in a (thread) shared data structure (e.g. a cache!).Contrary
B
12

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.

Brake answered 18/3, 2016 at 16:1 Comment(14)
I have included my Spring context configuration and ehcache.xml config in my question. I dispute your claim that my cache is not a second-level cache but feel free to point out where in my configs I have failed to create such a cache. Also what do you mean by "play around with hibernate.enable_lazy_load_no_trans"? Can you provide a code example taht would fix the problem?Bromal
@Bromal @Cacheable(value = "main") public Item findItemById(String id) { ... } is a Spring cache. Hibernate is not aware of it (how could it be?). Take a look at the example link I provided related to configuration and usage of Hibernate L2 cache. Regarding hibernate.enable_lazy_load_no_trans, I would say it should be enough only to set the property to true. However, take a look at the comments in the linked answer, there were some Hibernate bugs reported related to that property, make sure you use Hibernate version that contains the fixes.Brake
Hi, I included the Hibernate version I'm using in my question so when you say "make sure you use Hibernate version that contains the fixes", am I using the version that contains the fixes?Bromal
@Bromal It seems that it is, at least according to the comments in HHH-8782. Of course, I suggest you try it and research it in more detail if you're going with that option.Brake
... and a non neglectable advantage of using the second-level cache within Hibernate, is that it is actually safe to use!Contrary
If we are not manipualting any of the properties of the returned cached entities, is thread-safety still a concern? What other issues do you have with the current approach?Bromal
@Bromal It is still not safe. Hibernate only specifies that entities are not supposed to be thread safe. There are no guarantees about what will happen if you violate that. Maybe the state of proxies is changed at dirty time check for example. Or, if it is not changed now, maybe in future Hibernate versions it will be. Anyway, I edited my answer to include a suggestion how you could safely store detached entity instances in the custom cache.Brake
I haven't looked up the Spring code, but being outside the transaction scope doesn't mean the entity is actually detached (from the session)Contrary
@AlexSnaps Depending on the configuration. The default configurations in most ORM wrappers (including Spring) are such that transactions and sessions have the same scope (each transaction is bound to a new session).Brake
@DraganBozanovic Could well be, as I said I didn't read upon the source. Though I can think of SessionOpenInViewet al concepts that probably would cause that to be a false assumption. Further more, if that's the case, any entity returned during one tx, won't be == to the "same" entity in another tx, which for non concurrent tx may be somewhat of a nasty "side effect". Anyhow... Feels like ORM isn't well understood and that's the main problem with this "question".Contrary
@AlexSnaps I agree, but those concepts are rarely used compared to the sesson-per-transaction pattern. The point of my suggestion is that the easiest and safest way to retrieve the detached instance is to read it in another session. If the architecture is such that a new transaction does not create a new session, then a new session has to be created explicitly.Brake
I've tried to find how to do the following recommendation: "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." ... but haven't found any documented way anywhere. Can anyone provide at least some guidance? Thanks.Antlia
One simple way of handling this situation could be using @Transactional on the method with minimum transaction boundary because it will also impact on the performance of the code written in the transaction boundary so make it as short as possible .Spatial
@DraganBozanovic your comment indicates that there's a way to configure the 2nd level cache to "Cached entities are automatically attached to the subsequent sessions in which they are loaded." ..... but I cannot find out how to do that. Can you point me and Learner to how do to that?Tautology
B
11

What you implemented in your code snippets is a custom cache based on spring-cache. With your implementation you would need to take care of cache evictions, making sure that at the point when your object graphs will get cached they are properly loaded, etc. Once they get cached and the original hibernate session that loaded them is closed they'll become detached, you can no longer navigate unfetched lazy associations. Also, your custom cache solution in its current state would cache entity graphs, which is probably not what you want, since any part of that graph might change at a given time, and your cache solution would need to watch for changes in all parts of that graph to properly handle evictions.

The configuration you posted in your question is not Hibernate second-level cache.

Managing a cache is a complex endeavor and I don't recommend it doing it by yourself, unless you're absolutely sure what you're doing (but then you won't be asking this question on Stackoverflow).

Let me explain what is happening with when you get the LazyInitializationException: you marked one of your dao methods with @org.springframework.cache.annotation.Cacheable. What happens in this case is the following:

  1. Spring attaches an interceptor to your managed bean. The interceptor will intercept the dao method call, it will create a cache key based on the interceptor method and the actual method arguments (this can be customized), and look up the cache to see if there's any entry in the cache for that key. In case there's an entry it will return that entry without actually invoking your method. In case there's no cache entry for that key, it will invoke your method, serializes the return value and store it in the cache.
  2. For the case when there was no cache entry for the key, your method will get invoked. Your method uses a spring provided singleton proxy to the thread bound EntityManager which was assigned earlier when Spring encountered the first @Transactional method invocation. In your case this was the getContent(...) method of another spring service bean. So your method loads an entity with EntityManager.find(). This will give you a partially loaded entity graph containing uninitialized proxies and collections to other associated entities not yet loaded by the persistence context.
  3. Your method returns with the partially loaded entity graph and spring will immediately serialize it for you and store it in the cache. Note that serializing a partially loaded entity graph will deserialize to a partially loaded entity graph.
  4. On the second invocation of the dao method marked with @Cacheable with the same arguments, Spring will find that there is indeed an entry in the cache corresponding to that key and will load and deserialize the entry. Your dao method will not be called since it uses the cached entry. Now you encounter the problem: your deserialized cached entity graph was only partially loaded when you stored in the cache, and as soon as you touch any uninitialized part of the graph you'll get the LazyInitializationException. A deserialized entity will always be detached, so even if the original EntityManager would be still open (which is not), you would still get the same exception.

Now the question is: what can you do to avoid the LazyInitializationException. Well, my recommendation is that you forget about implementing a custom cache and just configure Hibernate to do the caching for you. I will talk about how to do that later. If you want to stick with the custom cache you tried to implement, here's what you need to do:

Go through your whole code base and find all invocations of your @Cacheable dao method. Follow all possible code paths where the loaded entity graph is passed around and mark all parts of the entity graph which ever gets touched by client code. Now go back to your @Cacheable method and modify it so that it loads and initializes all parts of the entity graph that would ever get possibly touched. Because once you return it and it gets serialized, and deserialized later, it will always be in a detached state so better make sure all possible graph paths are properly loaded. You should already feel how impractical this will end up. If that still didn't convince you not to follow this direction, here's another argument.

Since you load up a potentially big chunk of the database, you will have a snapshot of that part of the database at the given time when it got actually loaded and cached. Now, whenever you use a cached version of this big chunk of the database, there's is a risk that you are using a stale version of that data. To defend from this, you would need to watch for any changes in the current version of that big chunk of the database you just cached and evict the whole entity graph from the cache. So you pretty much need to take into account which entities are parts of your entity graph and set up some event listeners whenever those entities are changed and evict the whole graph. None of these issues are present with Hibernate second-level cache.

Now back to my recommendation: set up Hibernate second-level cache

Hibernate second-level cache is managed by Hibernate and you get eviction management from hibernate automatically. If you have Hibernate second-level cache enabled, Hibernate will cache the data needed to reconstruct your entities and, if - when seeking to load an entity from the database - it finds that it has a valid cache entry for your entity, it will skip hitting the database and reconstruct your entity from its cache. (Mark the difference to caching an entity graph with its possibly unfetched associations and uninitialized proxies in your custom cache solution). It will also replace stale cache entries when you update an entity. It does all sorts of things related to managing the cache so that you don't have to worry about it.

Here's how can you enable Hibernate second-level cache: in addition to your configuration do the following:

  1. In addition to the hibernate properties you already have for second-level management, 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 could add a shared-cache-mode configuration option to your persistence.xml (since you didn't post it, I assumed you don't use it hence the previous alternative; the following one is preferred though):

    <persistence-unit name="default">
        <!-- other configuration lines stripped -->
    
        <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
    
        <!-- other configuration lines stripped -->
    </persistence-unit>
    
  2. Add javax.persistence.@Cacheable annotation to your @Entity classes you want to be cacheable.
  3. If you want to add caching for collection valued associations which Hibernate doesn't cache by default, you can add a @org.hibernate.annotations.Cache annotation (with a proper cache concurrency strategy choice) 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;
    

See Improving performance/The Second Level Cache in the Hibernate Reference Documentation for further details.

This is a nice informative article about the subject: Pitfalls of the Hibernate Second-Level / Query Caches

I have put together a small project based on your posted code snippets which you can check out to see Hibernate second-level cache in action.

Bouse answered 23/3, 2016 at 1:4 Comment(21)
From what I'm reading about ENABLE_SELECTIVE (docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/…), even without specifyhing this, the default behavior is to cache entitites annotated with @Cacheable. However, I'm failing to see how this applies to the methods I have applied with the same Cacheable annotation and still failing ot understand further how it avoids the lazy loading problem (I tried adding this anyway but the lazy loading problem I described remains).Bromal
Regarding ENABLE_SELECTIVE: yes, the docs say it's the default, yet if I didn't specify it, Hibernate did no second-level caching. Regarding how my answer applies to your question: since you explicitly mentioned "second-level" in your question title, I assumed what you really want is Hibernate second-level caching. If that's not what you want, that's fine, but then your question is misleading. As I pointed out in my answer, your code snippets shows a custom cache solution. That's fine as well, custom caching solutions have their place in enterprise apps. [continued...]Cahoot
[continued] But your approach to do that is erroneous in many ways, and I pointed out some of those problems. You'll need to decide where you want to go, but I'm absolutely sure that the way you started with it is wrong and you'll run into many problems with it, should you go on with that route. However, if you choose to go with the normal Hibernate second-level cache you'll get proper caching out of the box, transaction semantics will be respected, it's safer, it's way easier (a few configuration options to specify). The positives are overwhelming.Cahoot
@Bromal "However, I'm failing to see how this applies to the methods I have applied with the same Cacheable annotation" Your @Cacheable is probably org.springframework.cache.annotation.Cacheable. The one used for Hibernate L2C is javax.persistence.Cacheable. These are two different annotations (putting the javax one on a method would even not compile).Brake
If you still think you're better off solving your LazyInitializingException and go on with that solution, that's easy: you just need to make sure that all lazy proxies and all lazy collections are initialized BEFORE your @Cacheable method ends AND before the declarative transaction (@Transactional) is ended. That can be achieved with either 1. JPA fetch plans, 2. a custom query instead of find(), specifying join fetches to fetch the whole graph, 3. navigating all parts of the whole graph you'll touch later outside the original transaction to initialize the proxies and lazy collections.Cahoot
Right now, I have marked many DAO methods with "org.springframework.cache.annotation.Cacheable". I can't tell from your answer what I replace that with if I intend for the results of those methods to be cached? Also, Spring ehcache provides the "org.springframework.cache.annotation.CacheEvict" annotation for eviction, so if I changed the approach to what you suggest, what replaces taht annotation?Bromal
Ok @Dave, I edited my answer and provided some background about what happens in your spring-cache based custom cache. If you read it carefully, you should see why would be a huge mistake on your part to follow that path, instead of properly setting up hibernate second-level cache and forget about all the hassle. If that still doesn't convince you, I think I'm gonna give up on you. I'm faithful though that you'll change your mind.Cahoot
BTW, did you even try my example project on github? It does a nice demo on how hibernate second-level cache will avoid hitting the DB when you load the same entity for the second time, without any lazy initialization exception. It's pretty much the same code as you had in your example, I only changed/added the necessary stuff. It does the demo on embedded in-memory H2 database so you don't have to worry about plugging it in to your own DB. It's just a simple demo with logs to prove what happens between Hibernate and the DB and how caching affects it.Cahoot
Yes, sure, Spring Ehcache is terrible. Ok we all agree on that. But my question remains -- what do I replace the annotated DAO methods with? In your project you do not annotated the lone DAO method ("findById"). I have tons of these that are more sophisticated, e.g. "findByCriterABAndC") and if I switch to your approach I still want to cache these DAO methods and evict properly . Marking @Cacheable at teh entity level alone will not do the trick for those DAO methods.Bromal
Did you read my edit to my answer? I went to great lengths in explaining the problem and what you need to do if you stick to your solution. Spring-Cache is not terrible. It's a simple thing that does just what it promises to do. You just seem to be missing the deep knowledge of each technology to understand the problems of your solution. I explained the problems with your approach in great details. I also told you what you need to do to avoid the exception if, for some reason, you still want to stick with your custom cache solution.Cahoot
A cache is used for remembering the results of expensive computations (in this case, the expensive part is communication with the database). Hibernate second-level cache will do just that. If you're willing to use caching for some other purpose, you're probably trying to use the wrong tool for the job.Cahoot
Yes the closest thing I can decipher from your answer about how to replace the "org.springframework.cache.annotation.Cacheable" annotation is the line where you say "go back to your @Cacheable method and modify it so that it loads and initializes all parts of the entity graph that would ever get possibly touched". Howver, because I'm "missing the deep knowledge of each technology", I don't know how to just "modify it so that it loads and initializes all parts of the entity graph". I cannot find any examples online demonstrating this which is why I ask the question here.Bromal
Yes. See my earlier comment on the same: "That can be achieved with either 1. JPA fetch plans, 2. a custom query instead of find(), specifying join fetches to fetch the whole graph, 3. navigating all parts of the whole graph you'll touch later outside the original transaction to initialize the proxies and lazy collections.". The 3rd way to do it is similar to what you do when you get the exception itself. for (Category category : item.getParent().getProduct().getCategories()). You just need to do this in your find method and all such possible entity graph navigations that may occur later.Cahoot
So you're sayhign keep my spring "@Cacheable" annotations and have custom Java code in each DAO finder method (either seting the FetchMmode or fetching all assocations as suggest with the categoreis)? So in order to successfully implement hibernate second level cache I have to add extra Java code to all my finder methods? That is quite a lot of extra work. I don't think it shoudl be that complicated, but if that is the only way, so be it.Bromal
:) No. I'm saying you should forget the whole spring cache thing and just go with hibernate second-level cache. But if you so insist to do caching based on spring-cache, I can't stop you from making self-inflicted wounds, then that's how you could do it (one of the 3 ways from my previous post).Cahoot
Ok I want to undertsand your solution so I'll ditch SPring ehcache. But in order to activate the Hibernate second-level cache I would need to implement one of the three suggestions you mention, correct? I don't need to mark the DAO methods with the Hibernate @Cacheable annotation? Btw, what do you think about Dragan's solution about adding "hibernate.enable_lazy_load_no_trans" to my config while keeping Spring ehcache? I'm tryhing that now and it seems to be working.Bromal
In the example I provided, there's a working example. If you run the class TestHibernateSecondLevelCache, you can see log entries. With an empty cache, at the first invocation of the contentService.getContent(...) method, there will be 4 select statements, which is Hibernate fetching the data from the database. At the second invocation, there are no select statements hitting the database, all entities are reconstructed from the cache. The configuration needed for this to work is very easy, it's detailed in my answer and it's also in the github project I provided.Cahoot
Regarding hibernate.enable_lazy_load_no_trans. I think it's a shaky configuration option. The hibernate team resisted to introduce the feature for very good reasons for years. I don't know why they ended up introducing it, probably because EclipseLink has a similar feature and they given up pressure from their users and followed suit. The feature itself is risky, because it breaks transaction semantics, because there will be no transactional guarantees about the entity graph fetched in multiple mini-transactions hitting the database.Cahoot
It also breaks referential integrity of the entity graph, because every time you touch an uninitialized proxy, it will open a new EntityManager. The new EntityManager will obviously not know about entities fetched by other EntityManagers in previous transactions, so it will end up creating new instances for the same entity, so you could end up with two different objects in your entity graph representing the same entity. This will obviously cause problems if you want to update those entities later, as Hibernate won't accept such an entity graph and will give you an error.Cahoot
Oh, and the whole thing will make your cache solution useless, as you'll practically store only those entities from your graph in the cache which are already loaded, the rest will be fetched every time you touch an uninitialized entity or collection. The whole thing will be a mess, trust me on this, and you would do it for no reason. Just stick with the cache Hibernate provides, easy to set up, you should be up and running within hours.Cahoot
You're the main man, but it proved too much to recode my entire application away from Spring-ehcahe. You think those guys would have said something about the fact it is totally unsuitable for working witih Hibernate managed entities.Bromal
S
3

The problem is that you are caching references to objects which are loaded lazily. Cache the object once it is all loaded or do not use the cache at all.

Here is how you could load the categories manually before caching it:

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

Also a better caching strategy would be to have the cache at the service level of your application instead of the DAO level or no cache at all.

Your issue is caused by the following events:

An Item is being retrieved without its categories then put in the cache in transaction 1. In transaction 2, you call the same method and retrieve the Item and try to read its categories. At that moment hibernate tries to read the categories from transaction 1 which is associated to the Item object but transaction 1 is already completed so it fails.

Scevo answered 18/3, 2016 at 23:47 Comment(3)
Hi, Ok, so when I said in my question, "Also, it is NOT an option o make the above association eager (instead of lazy)", neglecting the gramatically incorrect part of that sentence, was that really not enough to convey what I intended? Also, our service methods access the user session so we cannot cache data at the service level because different users would require different results (we use DWR for our AJAX calls if you're wondering why we do this).Bromal
See my comment above, don't store managed entities in the cache... the problem you have right now it the least of the ones you're about to have.Contrary
I edited my post to remove the option that you do not want. Why are you required to use the cache in this case? Maybe just a performance improvement of the code (smaller more specific entities or optimization of processing logic) could fix the need for a cache in this case.Scevo
M
3

I used simple type cache with this config as below:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

spring.jpa.open-in-view=true

spring.cache.type=simple
Mercedezmerceer answered 8/11, 2019 at 10:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.