Wednesday, 5 November 2014

Caching in Hibernate

Why caching is required?
To improve the performance the enterprise applications often it’s required to cache the data from database somewhere in the RAM or hard drive. This will boost the response time of application as most of the data accessed from the cached location instead of hitting the database for each and every request. For example suppose application displays configuration related data which most of the time will not changes often. This data can be cached for the very first request and displayed later.Now we can think of Hibernate how it manages caching in application.
Hibernate supports two types of caching:

1.       First level caching and

2.       Second level caching

First level caching:
By default hibernate API supports First level caching by session. But scope of cached data limited for that session only.
Second Level Cache:
Session Factory holds the second level cache data. Cached data will be available for entire application. Configurations required to enable the second level cache. There are different vendors available in the market to provide caching implementation.
1.       EH cache (Easy Hibernate Cache)

2.       Swaram Cache

3.       JBoss Cache

4.       OS Cache




How does Second Level Cache Works?
Entity objects never cached instead entity data will be cached in the system.  The data is stored in dehydrated format and it looks like hash map where key will be entity id and values will be list of primitive values.
*-----------------------------------------*
|          Person Data Cache              |
|-----------------------------------------|
| 1 -> [ "Suresh" , "Q" , "Public" , null ] |
| 2 -> [ "Mahesh" , "D" , "Public" ,  1   ] |
| 3 -> [ "Ramesh" , "N" , "Public" ,  1   ] |
*-----------------------------------------*

Sample EHcache xml file:
 <?xml version="1.0" encoding="UTF-8"?>

xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"

monitoring="autodetect" dynamicConfig="true">

<diskStore path="java.io.tmpdir/ehcache" />

<defaultCache maxEntriesLocalHeap="10000" eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30"

maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120"

memoryStoreEvictionPolicy="LRU" statistics="true">

<persistence strategy="localTempSwap" /></defaultCache>

<cache name="com.gps.model.FoodItems" maxEntriesLocalHeap="10000" eternal="false"

timeToIdleSeconds="5" timeToLiveSeconds="10">

<persistence strategy="localTempSwap" />

</cache>

<cache name="org.hibernate.cache.internal.StandardQueryCache"

maxEntriesLocalHeap="5" eternal="false" timeToLiveSeconds="10">

<persistence strategy="localTempSwap" />

</cache>

<cache name="org.hibernate.cache.spi.UpdateTimestampsCache"maxEntriesLocalHeap="5000" eternal="true">
<persistence strategy="localTempSwap" />

</cache>
</ehcache>

configuration can be reffered by below settings.
<property name="net.sf.ehcache.configurationResourceName">/Ehcache.xml</property>
How does Query Cache Works?
Conceptually query cache works like hash map where key will be query text along with parameter values and value will be list of entity IDs that match the query.
*----------------------------------------------------------*
|                       Query Cache 
                                              | Value
           KEY                                |
|----------------------------------------------------------|
| ["from dept1 where id=?", ["1"] ] -> [1, 2] ] |
*----------------------------------------------------------*
Some queries don't return entities instead of they return only primitive values. In those cases the values themselves will be stored in the query cache. The query cache gets populated when a cacheable JPQL/HQL query gets executed.

Note:If EHCache settings are not provided default settings will considered.


Database for Demo purpose:
Download H2 database and install. Open link http://localhost:8082 and login to test schema.

 
Steps for Query Level Cache
Step 1: Create an entity DepartmentEntity and annotate as highlighted in blue.
package hibernate.test.dto;
import java.io.Serializable;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;


@Entity (name = "dept1")
@Cacheable
@Table(name = "DEPARTMENT", uniqueConstraints = {
@UniqueConstraint(columnNames = "ID"),
@UniqueConstraint(columnNames = "NAME") })

@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="CRDEPT") public class DepartmentEntity implements Serializable {

private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "ID", unique = true, nullable = false)
private Integer id;
@Column(name = "NAME", unique = true, nullable = false, length = 100)
private String name;
public Integer getId() {
return id;

}
public void setId(Integer id) {

this.id = id;

}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Step 2: Create hibernate configuration file.
Below are the properties needs to be explicitly declared in the configuration file.
hibernate.cache.provider_class----->which cache provider needs to be allowed
hibernate.cache.use_query_cache------>explicitly allow query cache
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property><property name="hibernate.cache.use_query_cache">true</property>
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
                <session-factory>
                                <property name="hibernate.connection.driver_class">org.h2.Driver</property>
                                <property name="hibernate.connection.url">jdbc:h2:tcp://localhost/server~/test</property>
                                <property name="hibernate.connection.password">password</property>
                                <property name="hibernate.connection.username">sa</property>
                                <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
                                <property name="show_sql">true</property>
                                <property name="hbm2ddl.auto">update</property>
                                <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
                                <property name="hibernate.cache.use_query_cache">true</property>
                                <mapping class="hibernate.test.dto.DepartmentEntity"></mapping>
                </session-factory>
</hibernate-configuration>
Step 3: executing queries.
Set the query as catchable.  Here HQL is ---->from dept1. Once the query is executed result will be cached and if same query is executed in second session no SQL sent to DB.
 
                                           // Open the hibernate session
                                                                        Session session = HibernateUtil.getSessionFactory().openSession();
                                                                        session.beginTransaction();
                                                                        //Query q = session.createQuery("from dept1 where ID=:dpt_id ").setParameter("dpt_id", 1);
                                                                        Query q = session.createQuery("from dept1");
                                                                        //Query q = session.createQuery("select count(*)from dept1");
                                                                        q.setCacheable(true);
                                                                        //q.setCacheRegion("dept1");
                                                                        List<DepartmentEntity> listobjects1 = null; 
                                                                        listobjects1 = q.list();
                                                                        Iterator<DepartmentEntity> it1 = listobjects1.iterator();
                                                                        System.out.println("First Session Data:");
                                                                        while (it1.hasNext())
                                                                        {
                                                                                                DepartmentEntity departmentEntity = (DepartmentEntity) it1.next();
                                                                                                System.out.println("Department Names-->"+ departmentEntity.getName());
                                                                        }
                                                                        session.getTransaction().commit();
                                                                        session.close();
                                                                        Session session1 = HibernateUtil.getSessionFactory().openSession();
                                                                        session1.beginTransaction();
                                                                        //Query q1 = session1.createQuery("from dept1 where ID=:dpt_id").setParameter("dpt_id", 50);
                                                                        Query q1 = session1.createQuery("from dept1");
                                                                        //Query q1 = session1.createQuery("select count(*)from dept1");
                                                                        q1.setCacheable(true);
                                                                        //q1.setCacheRegion("dept1");
                                                                        List<DepartmentEntity> listobjects11 = null;
                                                                        listobjects11 = q1.list();
                                                                        Iterator<DepartmentEntity> it11 = listobjects11.iterator();     
                                                                        System.out.println("Second Session Data:");
                                                                        while (it11.hasNext())
                                                                        {
                                                                                DepartmentEntity departmentEntity = (DepartmentEntity) it11.next();
                                                                                System.out.println("Department Names-->"+ departmentEntity.getName());
                                                                        }
                                                                        session1.getTransaction().commit();
                                                                        session1.close();
Result:
As we can see only one time SQL sent to DB.
log4j:WARN No appenders could be found for logger (org.hibernate.cfg.annotations.Version).

log4j:WARN Please initialize the log4j system properly.

Hibernate: select department0_.ID as ID0_, department0_.NAME as NAME0_ from DEPARTMENT department0_

First Session Data:

Department Names-->        Human Resource

Department Names-->        Dev

Department Names-->        QA

Department Names-->        QA_1

Second Session Data:

Department Names-->        Human Resource

Department Names-->        Dev

Department Names-->        QA

Department Names-->        QA_1