Tuesday, July 17, 2012

Hibernate Caching Techniques


While working with Hibernate web applications we will face so many problems in its performance due to database traffic. That to when the database traffic is very heavy. Actually hibernate is well used just because of its high performance only. So some techniques are necessary to maintain its performance. Caching is the best technique to solve this problem.  It is same as browser cache.  Whenever the request placed for one resource, it will hit the database and loads the data from the database. If the request is for the same resource first it will check in the hibernate cache, if it is available then it won’t hit the database.  It retrieves the data from the cache only.  So the database calls will decrease and the response time increases.
We have 2 types of cache in hibernate.
  1. First level cache.
  2. Second level cache.
1). First level cache:-
  • First-level cache is associated with the Session object.  Hibernate uses this cache by default. No need to enable the first level cache explicitly and we can’t disable first level cache also.
  • Most commonly session object is associated with the Transaction. That is we will create the session object per transaction and close the session after completing of that transaction.
  • It minimizes the database calls that are present in that particular session that is in particular transaction.
  • In this instead of calling database for every modification on that particular entity object in that particular transaction, it updates the entity object at the end of that particular transaction.
Example: 
Consider the example that; there is Customer.java(entity) with the customerId, customerName as properties and there is corresponding table tbl_customer with customer_id and customer_name columns.

hibernate.cfg.xml

        
com.mysql.jdbc.Driver
root
jdbc:mysql://localhost:3306/test
root
org.hibernate.dialect.MySQLDialect
true



Entity Class:
package com.javamonkeys.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="tbl_customer")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="customer_id")
private Integer customerId;
@Column(name="customer_name")
private String customerName;
public Integer getCustomerId() {
 return customerId;
}
public void setCustomerId(Integer customerId) {
 this.customerId = customerId;
}
public String getCustomerName() {
 return customerName;
}
public void setCustomerName(String customerName) {
 this.customerName = customerName;
}

}
Main Program:
package com.javamonkeys;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import com.javamonkeys.entity.Customer;
public class MainProgram {
public static void main(String[] args) {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
try{   
Transaction tnxn = session.beginTransaction();
//trying to get the Customer object with customerId is 1.
//assuming that already there is customer info with the id 1.
Customer customer1 = (Customer)session.get(Customer.class,1);
Customer customer2 = (Customer)session.get(Customer.class,1);
Customer customer3 = (Customer)session.get(Customer.class,1);
//trying to update the same object multiple times.
customer1.setCustomerName("javamonkey ");
session.update(customer1);
session.update(customer1);
tnxn.commit();
}catch(Exception e){
e.printStackTrace();
}finally{
session.close();
}
}

}
         Assume that there is a record in the database ‘test’ with the customerId 1 in the tbl_customer table. In the line numbers 15, 16, 17 we are trying to retrieve the customer object from the database with id 1. In the three lines we are trying to get the same record. Hibernate hits the database in the line 15, but in the 16 and 17 line there will be no database call. That means it gets that same object from the first-level cache, as these three statements are the part of only one transaction and trying to get only one row from the database.  Hibernate intelligently detects that the minimum number of queries needs to be run.

        In the lines 20 and 21 we are trying to update the same object twice.  In this case if the name in the database is “javamonkey” hibernate won’t call database even one time. If the name is different than “javamonkey” then only once it hits the database to update the record.

Need of second level cache:

Problem with the first level cache is it is limited to session only.  How many sessions are opening, that many times hibernate hits the data base.  Alternatively we can minimize the database calls by putting the data in the session for the lookup values (data that won’t change frequently).  But consider there are 1000 users who are using the same application, then 1000 database calls for retrieving the same data. 

Say for example we have countries table, we need the countries values for entire application and those never change.  If we put these map or list in the session, to retrieve the counties map or list, there are 1000 database calls if we have 1000 users. 

Can’t we make only one database call and use those countries values for entire application…?

Yes, we can make only one database call and we can use those countries values for entire application, across users even with different applications also.  For this hibernate provides a powerful mechanism called second-level cache.

2). Second level cache:-

  • Second-level cache always associates with the Session Factory object.
  • As Session Factory object is only one for entire application, the data saved in the second level cache will be available for entire application.
  • It doesn't cares how many sessions are there, how many transactions are there in the application.  The data is available for all the transactions.
  • Here we can use query level cache also to cache the particular query results in the second level cache.
  • It never expires until the application is in running state.
  • By default the second level cache is set to false.  We can enable and disable as per our requirement.
  • For example if we are retrieving the data with the id 1 in different sessions.  In this case if second level cache is enabled; hibernate hits the data base only once.  For the next time on-wards it will search the data whether present in the second level cache, if it is there it won’t hit the database just it will pull the data from there.
  • It should be used for the lookup values that is, the values won’t change frequently.
  • We will take the Countries table contains the all country ids and country names in that table, Corresponding entity class is Countries.java and mapping file is Countries.hbm.xml (If you are not using the annotations).
Configuring Second level cache:
  • We need to add the following properties in the hibernate.cfg.xml to activate the second level cache.
true
org.hibernate.cache.EhCacheProvider
  • In the line 1 we are telling the hibernate to use the second level cache.
  • In the line 2 we are specifying the cache provider class.  We can implement the second level cache using different cache providers.  Those are
                    1.  EhCache (Easy Hibernate Cache)
                              2.  OSCache (Open Symphony Cache)
                        3.  Swarm Cache
                    4.  JBoss Tree Cache
  • Each implementation has its own advantages and disadvantages.
  • Here I am using EhCache. To use this EhCache we need to download the ehcache-core-.jar file and need to add this jar to the application build path.
  • To make the entity to be cacheable in second level cache, we need to add the annotation @Cacheable, @Cache below the @Entity annotation.
Entity class:

package com.javamonkeys.entity;
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 org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@Entity
@Table(name="countries")
@Cacheable
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Countries {
 @Id
 @GeneratedValue(strategy=GenerationType.AUTO)
 @Column(name="country_id")
 private Integer countryId;
 @Column(name="country_name")
 private String countryName;
 public Integer getCountryId() {
  return countryId;
 }
 public void setCountryId(Integer countryId) {
  this.countryId = countryId;
 }
 public String getCountryName() {
  return countryName;
 }
 public void setCountryName(String countryName) {
  this.countryName = countryName;
 }
}
By adding the @Cacheable annotation we are telling that hibernate that, this entity is cacheable and this entity has to consider for caching. 
By adding the @Cache we will configure the caching strategy. This annotation takes a parameter called usage where we can specify the caching strategy by using the enumeration. In hibernate, second-level cache we have four type of caching strategies. 
CacheConcurrencyStrategy.READ_ONLY: It’s the basic level of cache. This tells that the entity is read only. It is not going to write the data to the database. It is useful for the data that read frequently but never updated. It’s very simple. In this case hibernate doesn’t check whether update takes place, removing the cache or not. As it is read only, it the data is there in the cache it will provide that data from the cache for the application. For example we have the countries table and Countries Entity, in this case we will read data from database but we won’t update the Countries information as it is fixed.
CacheConcurrencyStrategy.READ_WRITE: It’s used whenever the data needs to be updated. It will check whether data is modified by application, so that we can get the modified data from the cache. That means whenever the data updated in the database hibernate updates the data in the cache also.
CacheConcurrencyStrategy. NONSTRICT_READ_WRITE: It is same as the read write strategy. But not strictly check the data modified or not in the application. 
CacheConcurrencyStrategy. TRANSACTIONAL: It is very strict about the data changes in the application or changes by other application. It checks the data whether it is modified or not for every transaction. It is almost same as session level cache. 
It is better to go for the READ_WRITE strategy.
  •  If you are not using the annotations we need to configure the caching strategy in the hbm file of that particular entity as follows.
hbm file:


    
                
            
            
        
        
            
        
       



  • In the line 3 we have configured the caching strategy to read-write strategy.

package com.javamonkeys;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import com.javamonkeys.entity.Countries;
public class MainProgram {
 public static void main(String[] args) {
  SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
  Session session=sessionFactory.openSession();
  Transaction tnxn = session.beginTransaction();
  Countries country = (Countries)session.get(Countries.class, 113);
  tnxn.commit();
  session.close();
  Session session1=sessionFactory.openSession();
  Transaction tnxn1 = session1.beginTransaction();
  Countries country1 = 
Countries)session1.get(Countries.class, 113);
  tnxn1.commit();
  session1.close();
 }
}
In the line number 12 we are retrieving the country with country id 113. And again we are retrieving the same data in the line number 17, but they are different sessions and different transactions. Generally hibernate has to hit the database for two times at line 12 and 17 but, As we implemented the second-level cache for Countries entity hibernate hits the database at line 12 only once. In the line 17 it won’t retrieve the data from the database instead, it will pull the same data from the second-level cache as it is already there in the cache. Even if I have 1000 places where the same data is required in the application, hibernate hits the database only once and used the same data from the cache will be used across the users. We are saving the 999 database calls in this case which has big impact of performance of application as well as database. 
Up to here everything is ok. But if we use the queries to retrieve the data from the database then this technique doesn’t work. You will wonder why this is not working and hitting the database two times to retrieve the data.
package com.javamonkeys;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import com.javamonkeys.entity.Countries;
public class MainProgram {
 public static void main(String[] args) {
  SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
  Session session=sessionFactory.openSession();
  Transaction tnxn = session.beginTransaction();
  String hql_query = "from Countries where countryId=:countryId";
  Query query = session.createQuery(hql_query);
  query.setInteger("countryId", 113);
  query.list();
  tnxn.commit();
  session.close();
  Session session1=sessionFactory.openSession();
  Transaction tnxn1 = session1.beginTransaction();
  String hql_query1 = "from Countries where countryId=:countryId";
  Query query1 = session1.createQuery(hql_query1);
  query1.setInteger("countryId", 113);
  query1.list();
  tnxn1.commit();
  session1.close();
 }

}
In the above case, in the output we can see two sql statements that are used to retrieve the data from the database with the country id 113 at line 16 and 24. To use the second level cache in this case we need to use the query cache technique. 
3. Caching the queries: - 

  • Query cache always use second level cache only. So if we want to use query cache we need to enable the second-level cache also.
  • Queries won’t cached by default. We need to tell hibernate explicitly to cache the queries. 
Configuring query cache: 

  • To configure the query cache we have to add the following line in the hibernate.cfg.xml including with the second level cache configuration lines as follows.
true 
org.hibernate.cache.EhCacheProvider 
true 

  • First two line to enable the second level cache and third line for enabling the query cache.
  • To cache the particular query we have to set query object cacheable for that we have to set property like query.setCacheable(true).

package com.javamonkeys;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import com.javamonkeys.entity.Countries;
public class MainProgram {
public static void main(String[] args) {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction tnxn = session.beginTransaction();
String hql_query = "from Countries where countryId=:countryId";
Query query =session.createQuery(hql_query).setCacheable(true);
query.setInteger("countryId", 113);
query.list();
tnxn.commit();
session.close();
Session session1=sessionFactory.openSession();
Transaction tnxn1 = session1.beginTransaction();
String hql_query1 = "from Countries where countryId=:countryId";
Query query1 = session1.createQuery(hql_query1).setCacheable(true);
query1.setInteger("countryId", 113);
query1.list();
tnxn1.commit();
session1.close();
}

}
In the lines 14 and 22 we have set that query to be cached. Now it hits the database only once when the line 14 got executed. When it executes line 22, as it set to cacheable in the line 22 first it will check the result in the query cache if it is there it will get the result from there only. 
But we need to set this flag to true in all places where we are using that particular query in the application otherwise it won’t check in the query cache.

4 comments:

  1. Could you give brief explanation on
    :org.hibernate.LazyInitializationException

    ReplyDelete
  2. I am not getting update's from java monkeys

    ReplyDelete
  3. please differentiate Named Queries, HQL, criteria queries
    -thank you:sai

    ReplyDelete
  4. It is really a great work and the way in which u r sharing the knowledge is excellent.Thanks for helping me to understand basic concepts. As a beginner in java programming your post help me a lot.Thanks for your informative article.java training in chennai

    ReplyDelete