Monday 2 July 2012

Hibernate Caching: Part II

In first part of Hibernate Caching, we discussed about overall caching mechanism, strategies, implementation & configurations. Now in part two, we will look into Query cache and Caching association.

In many applications we have certain data sets which are permanent (not changing frequently) in nature but still dynmic like currencies supported, languages supported, countries etc. In such cases, it is useful to cache the exact results of a query, rather than caching certain objects. These queries would return exactly the same data set each time it is called.

To do this, you need to set the hibernate.cache.use_query_cache property in the hibernate.cfg.xml file to true, as follows:

<property name="hibernate.cache.use_query_cache">true</property>

Then, you use the setCacheable() method as follows on any query you wish to cache:

public class CurrencyDAO {
   public List getCurrencies() {
      return SessionManager.currentSession().createQuery("from Currency as c order by c.name")
                           .setCacheable(true)
                           .list();
   }
}

To guarantee the non-staleness of cache results, Hibernate expires the query cache results whenever cached data is modified in the application. However, it cannot anticipate any changes made by other applications directly in the database. So you should not use any second-level caching (or configure a short expiration timeout for class- and collection-cache regions) if your data has to be up-to-date all the time.

Now think of associations in hibernate. Suppose we have a list of students enrolled for different courses. The following is the Hibernate mapping of the Student class:

<hibernate-mapping package="org.avid.hibernate.caching">
<class name="Student" table="tbl_student" dynamic-update="true">
<id name="id" type="long" unsaved-value="null" >
    <column name="student_id" not-null="true"/>
    <generator class="increment"/>
</id>
 
<property column="last_name" name="lastName" type="string"/>
<property column="first_name" name="firstName" type="string"/>

<set name="courses" table="tbl_course_enrollment" lazy="false">
     <key column="student_id"/>
     <many-to-many column="course_id" class="Course"/>
</set>
</class>
</hibernate-mapping>
 
In order to make Hibernate fetch courses when you read student, you can set lazy to true. In practice, deactivating lazy loading is not a good idea. It again depends on your requirement and your EXPERTISE. Let's write DAO for Student while lazy loading is set to false:

public class StudentDAO {
   public List getStudents() {
         return SessionManager.currentSession().createQuery("from Student").list();
   }
} 
 
If you test the code, it will take too much of time even to load 50 or so students. This is typical of the N+1 query problem. Each query on the Student table is followed by literally hundreds of queries on the Course table. Whenever Hibernate retrieves a Student from the cache, it reloads all the associated courses. Let's first activate read/write caching on the Student class as follows:

<hibernate-mapping package="org.avid.hibernate.caching">
   <class name="Student" table="tbl_student" dynamic-update="true">
       <cache usage="read-write"/>
       ... 
   </class>
</hibernate-mapping>
 
We can also activate caching on the Course class. Read-only caching should do here:

    <class name="Language" table="Course" dynamic-update="true">
        <cache usage="read-only"/> ... </class> 
    </hibernate-mapping>
 
Then, you will need to configure the cache rules by adding the following entries to the ehcache.xml file:

<cache name="org.avid.hibernate.caching.Student" eternal="false" 
        overflowToDisk="false" timeToIdleSeconds="300" timeToLiveSeconds="600" />

<cache name="org.avid.hibernate.caching.Course" maxElementsInMemory="100" 
        eternal="true" overflowToDisk="false" />
 
This is fine, but it doesn't solve the N+1 query problem: 50 or so extra queries will still be executed whenever you load a Student. This is a case where you need to activate caching on the Course association in the Course.hbm.xml mapping file, as follows:

<hibernate-mapping package="org.avid.hibernate.caching">
 <class name="Student" table="tbl_student" dynamic-update="true">
   <id name="id" type="long" unsaved-value="null" > 
       <column name="student_id" not-null="true"/> 
       <generator class="increment"/> 
   </id> 

   <property column="last_name" name="lastName" type="string"/>
   <property column="emp_firstname" name="firstname" type="string"/> 


   <set name="courses" table="tbl_course_enrollment" lazy="false">
       <cache usage="read-write"/> 
       <key column="student_id"/>
       <many-to-many column="course_id" class="Course"/>
   </set> 
 </class> 
</hibernate-mapping> 

This configuration would get better or optimal performance.

Finally caching is a powerful technique, and Hibernate provides a powerful, flexible, and unobtrusive way of implementing it. Even the default configuration can provide substantial performance improvements in many simple cases.

However, like any powerful tool, Hibernate needs some thought and fine-tuning to obtain optimal results, and caching—like any other optimization technique—should be implemented using an incremental, test-driven approach. When done correctly, a small amount of well executed caching can boost your applications to their maximum capacities.
 

0 comments:

Post a Comment