Search this Java developer blog:

Hibernate Interview questions and answers: cacheing

Q. What is a second-level cache in Hibernate?
A. Hibernate uses two different caches for objects: first-level cache and second-level cache. First-level cache is associated with the Session object, while second-level cache is associated with the SessionFactory object. By default, Hibernate uses first-level cache on a per-transaction basis. Hibernate uses this cache mainly to reduce the number of SQL queries it needs to generate within a given transaction. For example, if an object is modified several times within the same transaction, Hibernate will generate only one SQL UPDATE statement at the end of the transaction, containing all the modifications. The second-level cache needs to be explicitly configured. Hibernate provides a flexible concept to exchange cache providers for the second-level cache. By default Ehcache is used as caching provider. However more sophisticated caching implementation can be used like the distributed JBoss Cache or Oracle Coherence.

The Hibernate configuration looks like:

<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

The ehcache.xml can be configured to cache objects of type com.myapp.Order as shown below

<cache name="com.myapp.Order"
   maxElementsInMemory="300"
   eternal="true"
   overflowToDisk="false"
   timeToIdleSeconds="300"
   timeToLiveSeconds="300"
   diskPersistent="false"
   diskExpiryThreadIntervalSeconds="120"
   memoryStoreEvictionPolicy="LRU"       
/>

second-level cache reduces the database traffic by caching loaded objects at the SessionFactory level between transactions. These objects are available to the whole application, not just to the user running the query. The 'second-level' cache exists as long as the session factory is alive. The second-level cache holds on to the 'data' for all properties and associations (and collections if requested) for individual entities that are marked to be cached. It is imperative to implement proper cache expiring strategies as caches are never aware of changes made to the persistent store by another application. he following are the list of possible cache strategies.

  • Read-only: This is useful for data that is read frequently, but never updated. This is the most simplest and best-performing cache strategy.
  • Read/write: Read/write caches may be appropriate if your data needs to be updated. This carry more overhead than read-only caches. In non-JTA environments, each transaction should be completed when session.close() or session.disconnect() is called.
     
  • Nonstrict read/write: This is most appropriate for data that is read often but only occasionally modified.This strategy does not guarantee that two transactions won't simultaneously modify the same data.
     
  • Transactional: This is a fully transactional cache that may be used only in a JTA environment.


It can be enabled via the Hibernate mapping files as shown below:

<class name="com.myapp.Order">

    <cache usage="read-write"/>
    ....
</class>

Note: The usage options are: transactional|read-write|nonstrict-read-write|read-only. The cache can also be enabled at different granular level (e.g. parent, children, etc). The active orders will be cached for 300 seconds.



Q. How does the hibernate second-level cache work?
A. Hibernate always tries to first retrieve objects from the session and if this fails it tries to retrieve them from the second-level cache. If this fails again, the objects are directly loaded from the database. Hibernate's static initialize() method, which populates a proxy object, will attempt to hit the second-level cache before going to the database. The Hibernate class provides static methods for manipulation of proxies.


public final class Hibernate extends Object {
    ....
 public static void initialize(Object proxy)  throws HibernateException 
    .... 
}


As a consequence of using the Hibernate second-level cache, you have to be aware of the fact that each call of a data access method can either result in a cache hit or miss. So, configure your log4j.xml to log your hits and misses.

<logger name="org.hibernate.cache">
   <level value="DEBUG" />
</logger>

Alternatively, you can use Spring AOP to log the cache access on your DAO methods.


The second level cache is a powerful mechanism for improving performance and scalability of your database driven application. Read-only caches are easy to handle, while read-write caches are more subtle in their behavior. Especially, the interaction with the Hibernate session can lead to unwanted behavior.



Q. What is a query cache in Hibernate?
A. The query cache is responsible for caching the results and to be more precise the keys of the objects returned by queries. Let us have a look how Hibernate uses the query cache to retrieve objects. In order to make use of the query cache we have to modify the person loading example as follows.

Query query = session.createQuery("from Order as o where o.status=?");
query.setInt(0, "Active");
query.setCacheable(true); // the query is cacheable
List l = query.list();

You also have to change the hibernate configuration to enable the query cache. This is done by adding the following line to the Hibernate configuration.

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


Q. What are the pitfalls of second level and query caches?
A. Memeory is a finite resource, and over use or incorrect useage like cacheing the Order object and all its referenced objects can cause OutOfMemoryError. Here are some tips to overcome the pitfalls relating to cacheing.

1. Set entity’s keys as query parameters, rather than setting the entire entity object. Critreia representations should also use identifiers as parameters. Write HQL queries to use identifiers in any substitutable parameters such as WHERE clause, IN clause etc.

In the example below, the entire customer and everything he/she references would be held in cache until either the query cache exceeds its configured limits and it is evicted, or the table is modified and the results become dirty.

final Customer customer = ... ;
final String hql = "FROM Order as order WHERE order.custOrder = ?"
final Query q = session.createQuery(hql);
q.setParameter(0, customer);
q.setCacheable(true);

Instead of setting the whole customer object as shown above, just set the id.

final Order customer = ... ;
final String hql = "from Order as order where order.cusomer.id = ?"
final Query q = session.createQuery(hql);
q.setParameter(0, customer.getId());
q.setCacheable(true);

2. Hibernate's query cache implementation is pluggable by decorating Hibernate's query cache implementation. This involves overriding the put( ) method to check if a canonical equivalent of a query results object already exist in the Object[][], and assign the same QueryKey if it exists.


3. If you are in a single JVM using in memory cache only, use hibernate.cache.use_structured_entries=false in your hibernate configuration.


Here are some general performance tips:

1. Session.load will always try to use the cache. Session.find does not use the cache for the primary object, but cause the cache to be populated. Session.iterate always uses the cache for the primary object and any associated objects.

2. While developing, enable the show SQL and monitor the generated SQL.

<property name="show_sql">true</property>

Also enable the "org.hibernate.cache" logger in your log4j.xml to monitor cache hits and misses.




More Hibernate Related Interview Questions and Answers

4 comments:

  1. NIce Artical, but still I am expecting some more example explanation

    ReplyDelete
  2. Great Article. Really helpful...

    ReplyDelete
  3. Very Nicely explained !!!!

    GoooD!

    Thanks & Keep Updating!!

    ReplyDelete

5 recent Java developer posts