Hibernate : Caching

Hibernate offers caching functionality which is designed to reduce the number of database hits as many as possible to give a better performance for performance critical applications.

HIbernate_cache

First-level cache

Fist level cache in hibernate is enabled by default and you do not need to do anything to get this functionality working. In fact, you can not disable it. It is associated with Session object, and is available only till session objects lives. When we query an entity first time, it is retrieved from database and stored in first level cache associated with hibernate session. If we query same object again with same session object, it will be loaded from cache and no sql query will be executed. Lets see this by an example. Our Employee and hibernate-cfg.xml remains the same. HibernateFirstLevelCacheTest.java:


package com.hibernate.firstLevelCache;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateFirstLevelCacheTest {

public static void main(String[] args) {

SessionFactory sessionFactory = new Configuration().configure(
"/com/hibernate/firstLevelCache/hibernate.cfg.xml")
.buildSessionFactory();
Session session1 = sessionFactory.openSession();
session1.beginTransaction();
Employee employee1=(Employee) session1.get(Employee.class, 2);
System.out.println(employee1);
Employee employee2=(Employee) session1.get(Employee.class, 2);
System.out.println(employee2);
session1.getTransaction().commit();
session1.close();

Session session2 = sessionFactory.openSession();
session2.beginTransaction();
Employee employee3=(Employee) session2.get(Employee.class, 2);
System.out.println(employee3);
session2.getTransaction().commit();
session2.close();
}

}

Output:


Hibernate: select employee0_.id as id1_0_0_, employee0_.name as name2_0_0_ from Employee employee0_ where employee0_.id=?
Id=2 Name=Employee Name2 updatd
Id=2 Name=Employee Name2 updatd
Hibernate: select employee0_.id as id1_0_0_, employee0_.name as name2_0_0_ from Employee employee0_ where employee0_.id=?
Id=2 Name=Employee Name2 updatd

As you can see,  session1.get() call does not fire the select query again.  session2.get() being in a different session runs the select query again.

Though we can not disable the first level cache in hibernate, but we can certainly remove some of objects from it when needed. This is done using two methods :

  • evict() – is used to remove a particular object from cache associated with session. Try session1.evict(employee1) in the previous example and see that select query will be fired again for employee2.
  • clear() – is used to remove all cached objects associated with session. Try session1.clear();

Second-level cache

Second level cache is an optional cache and first-level cache will always be consulted before any attempt is made to locate an object in the second-level cache. Second level cache can be implemented in one of different ways:

  • Across different session in an application
  • Across application
  • Across clusters

Second level cache is created in session factory scope and is available to be used in all sessions which are created using that particular session factory.Any third-party cache can be used with Hibernate. Some of the Cache Implementations.

  • EhCache (Easy Hibernate Cache)
  • OSCache
  • SwarmCache
  • JBoss TreeCache

We would be using EhCache. We have to add these jars in our classpath. Go to the hibernate extacted files. Go to lib > optional >ehcache , and add all these jars to your classpath – ehcache-core-2.4.3.jar, hibernate-ehcache-4.3.1.Final.jar, slf4j-api-1.6.1.jar

hibernate.cfg.xml


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hibernateDB</property>
<property name="connection.username">root</property>
<property name="connection.password">admin</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>

<!-- Second-level cache -->
<property name="cache.use_second_level_cache">true</property>
<property name="cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</property>

<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">update</property>
<!-- Names the annotated entity class -->
<mapping class="com.hibernate.secondLevelCache.Employee"></mapping>
</session-factory>
</hibernate-configuration>

Notice the changes for second level change.

The second step we need to do is actually configure the entities that we want to be cached. Not all enties are cache by default. We do it by using @Cacheble. We can also metion what is the caching stratergy. There are following option:

  • None : No caching will happen.
  • Read-only -Useful for data that is read frequently but never updated.
  • Read-Write -Used when our data needs to be updated.
  • Nonstriict read-write – Needed if the application needs to update data rarely. Not that strict as Read-Write.
  • Transactional – Strictest of all

Changing Employee.java:


package com.hibernate.secondLevelCache;

import javax.persistence.Cacheable;
import javax.persistence.Entity;
import javax.persistence.Id;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity
@Cacheable
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
public class Employee {

@Id
private int id;
private String name;

@Override
public String toString() {
return "Id="+this.id+" Name="+this.name;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

HibernateSecondLevelCacheTest.java


package com.hibernate.secondLevelCache;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateSecondLevelCacheTest {

public static void main(String[] args) {

SessionFactory sessionFactory = new Configuration().configure(
"/com/hibernate/secondLevelCache/hibernate.cfg.xml")
.buildSessionFactory();
Session session1 = sessionFactory.openSession();
session1.beginTransaction();
Employee employee1=(Employee) session1.get(Employee.class, 2);
System.out.println(employee1);
session1.getTransaction().commit();
session1.close();

Session session2 = sessionFactory.openSession();
session2.beginTransaction();
Employee employee2=(Employee) session2.get(Employee.class, 2);
System.out.println(employee2);
session2.getTransaction().commit();
session2.close();
}

}

Output:


Hibernate: select employee0_.id as id1_0_0_, employee0_.name as name2_0_0_ from Employee employee0_ where employee0_.id=?
Id=2 Name=Employee Name2 updatd
Id=2 Name=Employee Name2 updatd

As you can see even if the session are different the select query ran only once.

Query caching

We have already enabled the second level cache. Till now we were using get() method to get the result. Lets use the Query for fetching the result with second level caching enabled. HibernateQueryCacheTest.java


package com.hibernate.queryCache;

import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import com.hibernate.hql.Employee;

public class HibernateQueryCacheTest {

public static void main(String[] args) {

SessionFactory sessionFactory = new Configuration().configure(
"/com/hibernate/queryCache/hibernate.cfg.xml")
.buildSessionFactory();
Session session1 = sessionFactory.openSession();
session1.beginTransaction();
Query query1=session1.createQuery("from Employee where id=4");
List<Employee> list1=query1.list();
session1.getTransaction().commit();
session1.close();

Session session2 = sessionFactory.openSession();
session2.beginTransaction();
Query query2=session2.createQuery("from Employee where id=4");
List<Employee> list2=query2.list();
session2.getTransaction().commit();
session2.close();
}

}

Output :


Hibernate: select employee0_.id as id1_0_, employee0_.name as name2_0_ from Employee employee0_ where employee0_.id=4
Hibernate: select employee0_.id as id1_0_, employee0_.name as name2_0_ from Employee employee0_ where employee0_.id=4

As you can see, that despite of second level enabled, hibernate is still firing 2 select queries for getting the result. This is because hibernate takes queries differently and do not get the object from the cache, i.e result of the query is not stored in the same place in the cache as it is stored for the get() method. So this is a 3rd kind of cache in hibernate.  We need to tell hibernate to set a particular query to cache.  Add following in hibernate.cfg.xml


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

Use query.setCacheable(true) to make it cachable. Changing HibernateQueryCacheTest.java


//same as previous

Query query1=session1.createQuery("from Employee where id=4");
query1.setCacheable(true);
List<Employee> list1=query1.list();
session1.getTransaction().commit();
session1.close();

Session session2 = sessionFactory.openSession();
session2.beginTransaction();
Query query2=session2.createQuery("from Employee where id=4");
query2.setCacheable(true);
List<Employee> list2=query2.list();

//same as previous

Output:


Hibernate: select employee0_.id as id1_0_, employee0_.name as name2_0_ from Employee employee0_ where employee0_.id=4

As you can see the select query will run only once. Note that if you remove query2.setCacheable(true); and runs , it will still run 2 select queries. This is beacause query1.setCacheable(true);  has set the result of this query to cache. Now query2 if not cacheble will not search in the cache memory and go to DB for result.  Therefore query2.setCacheable(true); need to be done which first searches in the cache memory (to check if any other query has setted it) and then to DB. In our case, query1 already did so query2 never went to DB.

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: