Friday, April 11, 2014

Hibernate second level caching for spring applications


This post explains how to manage hibernate second level cache. It is a comprehensive step by step guide which explains how to enable monitor and disable hibernate cache.

Some hard facts

1. By default hibernate uses second level cache when the entity is fetched by Id – that means all findbyX in your DAOs where X is anything other than primary key will not use cache.
2. Associations are not cached by default – that means if you have an association in your entity then database will be queried every time for fetching the association
3. Queries are cached only when Criteria.setCachable(true) or Query.setCachable(true) is explicitly set.


So here is your, Guide to enable hibernate caching
(assuming you are using annotations, for hbm files, use the configurations corresponding to these annotations)

1. Annotate "to be cached" entities with @Cache

2. Make sure that "to be cached" associations are annotated with @Cache
Example:

    @Cascade(org.hibernate.annotations.CascadeType.DELETE)
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private List<Template> templates;


3. For Criteria's  set Cachable as true;
    Example:

    public List<Template> findByWeightage(List<Long> weightages) {
        Criteria crit = getSession().createCriteria(Template.class);
        crit.add(Restrictions.in("weigtage", weightages));
        crit.setCacheable(true);
        return (List<Template>) crit.list();
    }


4. For Qeuries set Cachable as true
Example:

   SQLQuery query = getSession().createSQLQuery(“.......”);
   query.setCacheable(true);
   List list = query.list();


5. Mark your named queries as cachable by adding a hint
     hints = { @QueryHint(name = "org.hibernate.cacheable", value = "true") }

6. In your dialect properties add
    <prop key="hibernate.cache.use_query_cache">true</prop> <!--For query cache-->
    <prop key="hibernate.cache.default_cache_concurrency_strategy">read-only</prop>
    <prop key="hibernate.cache.use_structured_entries">true</prop> 
    <prop key="hibernate.cache.use_second_level_cache">true</prop>
    <!--following two entries for ehcache-->
    <prop       key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvider</prop>
    <prop key="net.sf.ehcache.configurationResourceName">ehcache.xml</prop>


7. Let spring manage the transactions, remove any transaction related configurations from dialect properties (be careful, if you disable the following configurations then you may need to adjust your spring transaction configurations )
    <!--prop key="hibernate.current_session_context_class"> thread </prop-->
    <!--prop key="hibernate.transaction.auto_close_session">true</prop-->
    <!--prop key="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</prop-->


How to verify the caching?

1. enable JMX in your application. For remote tomcat, add the following catalina options
     CATALINA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"

2. run jconsole with the required jars in your class-path


    jconsole -J-Djava.security.manager -J-Djava.security.policy==server.policy -J-Djava.class.path=/usr/lib/jvm/java-6-oracle/lib/jconsole.jar:/usr/lib/jvm/java-6-oracle/lib/tools.jar:/home/santhosh/trunk/commons/app/WEB-INF/lib/hibernate-core-3.6.0.Final.jar

3. If java security does not allow de-serializing the classes configure server.policy in class-path. Sample content given below
    grant codebase "file:/home/santhosh/trunk/commons/app/target/app/WEB-INF/lib/hibernate-core-3.6.0.Final.jar"
{
   permission java.security.AllPermission;
};
grant codebase "file:/usr/lib/jvm/java-6-oracle/lib/jconsole.jar"
{
   permission java.security.AllPermission;
};
grant codebase "file:/usr/lib/jvm/java-6-oracle/lib/tools.jar"
{
   permission java.security.AllPermission;
};


4. Enable hibernate statistics by adding the following in dialect propertis
   <prop key="hibernate.generate_statistics">true</prop>

5. Now you will be able to see the statistics using the Hibernate bean in jconsole


What if you want to disable the cache?

If you are going to comment the following three lines
 <prop key="hibernate.cache.use_structured_entries">true</prop>  
    <prop key="hibernate.cache.use_second_level_cache">true</prop> 
    <!--following two entries for ehcache-->
    <prop       key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvider</prop> 

Then you are going to get this
Invocation of init method failed; nested exception is org.hibernate.cache.NoCachingEnabledException: Second-level cache is not enabled for usage [hibernate.cache.use_second_level_cache | hibernate.cache.use_query_cache]


Solution is to set
<prop key="hibernate.cache.use_query_cache">false</prop>
 <prop key="hibernate.cache.use_second_level_cache">false</prop>

This is required since hibernate expects explicitly disabling cache when caching is defined at entity level

Other Possible Exceptions and solutions
 java.lang.ClassCastException: java.math.BigInteger cannot be cast to [Ljava.lang.Object;
        at org.hibernate.cache.StandardQueryCache.put(StandardQueryCache.java:106)

To fix this add scalar to your query
query.addScalar("employee.id", StandardBasicTypes.BIG_INTEGER);

Note: Spring version 3.2 and Hibernate version 3.6