-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 
Author Message
 Post subject: Need for (re)initializing objects when using 2nd-level cache
PostPosted: Wed Aug 16, 2006 6:47 pm 
Newbie

Joined: Wed Aug 16, 2006 1:17 pm
Posts: 3
I have a DAO layer that executes queries using DetachedCriteria. The mapping files have (by default) lazy="true". Hence, the queries formulated using DetachedCriteria have eager fetching enabled so as to fetch, create/initialize the domain objects as soon as the 'find' query is executed.

Here is an example of the how a typical DAO implementation looks like (fyi: A Subtest contains a collection of Items and each Item contains a Passage):
Code:
public List<Item> findBySubtestId(long subtestId) {
   DetachedCriteria itemCriteria = DetachedCriteria.forClass(Item.class);
   itemCriteria.setFetchMode("passage", FetchMode.JOIN);
   itemCriteria.setFetchMode("subtest", FetchMode.JOIN);

   DetachedCriteria subtestCriteria = sectionCriteria.createCriteria("subtest");
   subtestCriteria.add(Restrictions.eq("subtestId", subtestId));

   List<Item> results = this.getHibernateTemplate().findByCriteria(itemCriteria);

   return results;
}


The key idea behind the above code is to fetch a set of Items using a subtest ID. Setting fetch mode as FetchMode.JOIN fetches the desired objects eagerly. This is done primarily since we close the Hibernate session as soon as the DAO call is completed using Spring configuration.

All of the above works absolutely fine until the second level cache is introduced as follows:
Code:
public List<Item> findBySubtestId(long subtestId) {
   DetachedCriteria itemCriteria = DetachedCriteria.forClass(Item.class);
   itemCriteria.setFetchMode("passage", FetchMode.JOIN);
   itemCriteria.setFetchMode("subtest", FetchMode.JOIN);

   DetachedCriteria subtestCriteria = sectionCriteria.createCriteria("subtest");
   subtestCriteria.add(Restrictions.eq("subtestId", subtestId));

   //Enable query caching.
   itemCriteria.getExecutableCriteria(getSession()).setCacheable(true);

   List<Item> results = this.getHibernateTemplate().findByCriteria(itemCriteria);

   //results contains proxied objects when the query is executed for the second time.

   return results;
}



The above code executes fine for the first call (since the data is not cached yet). From the second-call onwards, I receive a lazy initialization exception.

I debugged and stepped through the code and found that I see Proxy objects from the second-call onwards due to which I receive the lazy initialization error.

To make the initialization error go away, I added a loop to step through the collection and initialize the desired objects. The modified code is shown below:
Code:
public List<Item> findBySubtestId(long subtestId) {
   DetachedCriteria itemCriteria = DetachedCriteria.forClass(Item.class);
   itemCriteria.setFetchMode("passage", FetchMode.JOIN);
   itemCriteria.setFetchMode("subtest", FetchMode.JOIN);

   DetachedCriteria subtestCriteria = sectionCriteria.createCriteria("subtest");
   subtestCriteria.add(Restrictions.eq("subtestId", subtestId));

   //Enable query caching.
   itemCriteria.getExecutableCriteria(getSession()).setCacheable(true);

   List<Item> results = this.getHibernateTemplate().findByCriteria(itemCriteria);

   // Initalization of objects returned by Hibernate.
   //  Required only when associations obtained from second-level cache
    for (Item item : results) {
      Hibernate.initialize(item);
      Hibernate.initialize(item.getPassage());
      Hibernate.initialize(item.getPassage().getTitle());
            this.getHibernateTemplate().getSessionFactory().getCurrentSession().evict(item);
   }

   return results;
}


What concerns me is the fact that I had to explicitly initialize the objects when I fetch the data from the second-level cache. Hibernate readily initializes the objects for me as indicated by the Detached Criteria query's fetch mode when executing it against the databae (the first run).

One possible explanation I have seen while reading one of the forum messages (which seems plausible) is that since second level cache stores data across sessions, it does not store objects (like how Hibernate's first level cache would), instead it stores persistent data in a disassembled format. Hence a new session is required to initialize the objects obtained from the cache.

Here's the question for which I am submitting this post:
If the query suggests that the fetch mode is eager/join, shouldn't Hibernate provide initialized objects even when it is fetching it from the second-level cache? Why does Hibernate differentiate in returning the collection of Items when obtaining from the database vs fetching from second-level cache?

In the above scenario, since adding second level cache was an after-thought, I have to now modify the code to now initialize the objects after the query is executed. I was hoping to see that adding second level cache would be transperant to the existing code and would require changes to only the Hibernate configurations.

BTW, to setup second-level cache (I am using OS Cache 2.3.1) the respective configurations were added to the session factory beans in the Spring configuration files along with setting 'entityCacheStrategies' for the respective domain objects.
Code:
   <prop key="hibernate.cache.provider_class">com.xxx.xxx.xxx.OSCacheProvider</prop>
   <prop key="hibernate.cache.use_query_cache">true</prop>



I am using Hibernate version: 3.0.5

-- Vinay


Top
 Profile  
 
 Post subject:
PostPosted: Wed Aug 16, 2006 11:27 pm 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
vinayj1 wrote:
One possible explanation I have seen while reading one of the forum messages (which seems plausible) is that since second level cache stores data across sessions, it does not store objects (like how Hibernate's first level cache would), instead it stores persistent data in a disassembled format. Hence a new session is required to initialize the objects obtained from the cache.

This is exactly correct.

vinayj1 wrote:
Here's the question for which I am submitting this post:
If the query suggests that the fetch mode is eager/join, shouldn't Hibernate provide initialized objects even when it is fetching it from the second-level cache? Why does Hibernate differentiate in returning the collection of Items when obtaining from the database vs fetching from second-level cache?

It would be nice if hibernate did this, but that would make the 2nd lvl cache must less flexible. It also introduces a much greater risk of stale objects being returned from the cache.

Hibernate is aware that there are many complexities to using the 2nd lvl cache. I'm of the opinion that that's exactly why it is turned off by default, and is fairly complex to turn on (have to set up the .cfg.xml file, and each cacheable query).

_________________
Code tags are your friend. Know them and use them.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 17, 2006 12:08 am 
Pro
Pro

Joined: Tue Aug 26, 2003 8:07 pm
Posts: 229
Location: Brisbane, Australia
tenwit wrote:
It would be nice if hibernate did this, but that would make the 2nd lvl cache must less flexible. It also introduces a much greater risk of stale objects being returned from the cache.


This would only be the case if Hibernate stored the associated objects in the 2nd level cache.

Would it be feasible to have Hibernate detect that the query was issued using an eager fetching strategy and do the legwork of initialising the loaded objects for us (similar to what Vinay's code is doing, but generically)?


P.S. Vinay: why do you issue the evict() call, when you're doing 2nd level caching?
(I'm not saying it's not necessary or anything - I'm a bit of a noob when it comes to the 2nd-level caching stuff).

_________________
Cheers,
Shorn.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 17, 2006 11:40 am 
Newbie

Joined: Wed Aug 16, 2006 1:17 pm
Posts: 3
tenwit wrote:
It would be nice if hibernate did this, but that would make the 2nd lvl cache must less flexible. It also introduces a much greater risk of stale objects being returned from the cache.


There are two points you raise above:
1. Makes the 2nde level cache less flexible: My understanding is that Hibernate is supposed to create objects off of the persistent data stored in the 2nd level cache and it is not the responsibility of the 2nd level cache to create the objects. So, any changes to be effected, would happen on the Hibernate end instead of the 2nd level cache. Am I correct in assuming that?

2. introduces a much greater risk of stale objects from cache: Once again, my understanding is that Hibernate is creating the objects once the data is read from the database or the second level cache. Since the second level cache does not store objects but rather persistent data only, it should be treated as a database that is local to the server instead of reaching out on the network.

All I am suggesting is that Hibernate treat creation of objects in the same way when fetching the data from database or 2nd lvl cahce if the query suggests eager fetching (rather than us, the developers, walking through the graph of objects and re-initializing them).


stolley wrote:
This would only be the case if Hibernate stored the associated objects in the 2nd level cache.

Would it be feasible to have Hibernate detect that the query was issued using an eager fetching strategy and do the legwork of initialising the loaded objects for us (similar to what Vinay's code is doing, but generically)?

Good to see that someone shares my thoughts :)

stolley wrote:
P.S. Vinay: why do you issue the evict() call, when you're doing 2nd level caching?
(I'm not saying it's not necessary or anything - I'm a bit of a noob when it comes to the 2nd-level caching stuff).

You are right stolley. The evict() piece is left in there from my initial tries. Once you perform
Code:
Hibernate.initialize(item);
eviction is no longer necessary. In other words, consider to be redundant. Please discard the evict() call.

-- Vinay


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 17, 2006 6:17 pm 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
You can't treat the cache exactly like an alternate data source. For example, if you load a object that contains a collection, the SQL would be something to the effect of
Code:
select x.*, y.*
from Table1 x
inner join Table2 y on y.Xid = x.id
where x.id = ?
You can find x in the cache because a single ID is provided in the query: if you find that id in the cache, you have your entity. However, you now have to do something like "select * from Y where Xid = ?", and you can't do that from the cache. The cache may contain some of the corresponding Table2 rows, and may leave some out, meaning that hibernate has to doublecheck with the DB before returning your Table1 entity.

To mitigate this risk, hibernate returns a normal proxied collection in the Table1 entity.

This could be changed as you both suggest, but I'm of the opinion that the risk of staleness is so much higher for a potentially-unbounded collection than it is for a single queried entity, that it's better to accept the laziness in order to get the correct results.

_________________
Code tags are your friend. Know them and use them.


Top
 Profile  
 
 Post subject: Possible solutions
PostPosted: Thu Aug 17, 2006 11:43 pm 
Newbie

Joined: Wed Aug 16, 2006 1:17 pm
Posts: 3
The fact the query has a fetchmode.join on it should indicate Hibernate that multiple requests to the database/2nd level cache is not desired.

I also agree that Hibernate can get in a pickle if only partial data is stored in 2nd level cache, but I would rather have an option to choose from:
1. Fetch whatever data it can from the 2nd level cache and then the rest from database. OR
2. Throw an exception that only partial data is available from the 2nd level cache. OR
3. Leave the current implementation of Hibernate (for backwards compatibility), ie., provide objects in a lazy fashion and leave it for the developer to handle initialization issues.

Such options can be made available for each query cache region for maximum flexibility.

-- Vinay


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.