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