05 June 2015

Introduction

In case of second level caching, object persists at the Session Factory Level. Hence, the persistance object will be accessible across all sessions once the data is cached. In this example, second level caching is implememted using EHCache. Once the application is looking for any persistance object, first it checks in the first level cache. If the data is not found then it goes to the second level cache. If not found in the second level cache also then it hits the database. This example shows the statistics of second level cache hit and miss.
Also note that in this example in memory database (HSQLDB) is used.

Required Software

  • JDK 1.7
  • Maven 2.2.x
  • Eclipse
  • Hibernate
  • EH Cache for second level caching

Step to write Code

In this example HSQLDB jar has been used. So no real database is required to run the stand alone application. Two employee records have been inserted into the in memory database using one session and retrieved the same data from the different session.

Create a maven project as shown below. If you are new to create maven project then follow this blog

The description of the files are given below

SL NO File Name Description
1 pom.xml Hibernate and EHCache dependency added in this file
2 hibernate.cfg.xml EHCache second level caching is plugged into this file
3 ehCache.xml EHCache second level configuration is done in this file
4 com.ashish.entity.EmployeeEntity This is the entity class where the concurrency strategy (Read only, read write, Nonrestricted Read Write, Transactional) is defined.
5 com.ashish.util.HibernateUtil This class will read configuration from hibernate.cfg.xml file and returns SessionFactory
6 com.ashish.main.MainApp This class contains the main method and inserts the record of two employees called Ashish, Ujan. Data is retrieved from the database using secong level caching strategy

pom.xml: Hibernate and EHCache dependency added in this file


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.ashish.hibernate</groupId>
	<artifactId>Hibernate2ndLevelCaching</artifactId>
	<version>1.0-SNAPSHOT</version>

	<repositories>
		<repository>
			<id>jboss</id>
			<url>http://repository.jboss.org/maven2</url>
		</repository>
	</repositories>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<!-- Hibernate core -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>4.3.6.Final</version>
		</dependency>

		<!-- Hibernate annotation -->
		<dependency>
			<groupId>hibernate-annotations</groupId>
			<artifactId>hibernate-annotations</artifactId>
			<version>3.3.0.GA</version>
		</dependency>

		<dependency>
			<groupId>hibernate-commons-annotations</groupId>
			<artifactId>hibernate-commons-annotations</artifactId>
			<version>3.0.0.GA</version>
		</dependency>

		<!-- Hibernate library dependecy start -->
		<dependency>
			<groupId>dom4j</groupId>
			<artifactId>dom4j</artifactId>
			<version>1.6.1</version>
		</dependency>

		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.1.1</version>
		</dependency>

		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.2.1</version>
		</dependency>

		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>2.2</version>
		</dependency>
		<!-- Hibernate library dependecy end -->

		<!-- HSQL database -->
		<dependency>
			<groupId>hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>1.8.0.10</version>
		</dependency>

		<!-- EHCache Core APIs -->
		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache-core</artifactId>
			<version>2.6.9</version>
		</dependency>
		<!-- Hibernate EHCache API -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-ehcache</artifactId>
			<version>4.3.5.Final</version>
		</dependency>
		<!-- EHCache uses slf4j for logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-simple</artifactId>
			<version>1.7.5</version>
		</dependency>
	</dependencies>
</project>

hibernate.cfg.xml: EHCache second level caching is plugged into this file


<?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.archive.autodetection">class,hbm</property>
		<property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>
		<property name="hibernate.show_sql">true</property>
		<property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
		<property name="hibernate.connection.username">sa</property>
		<property name="hibernate.connection.password"></property>
		<property name="hibernate.connection.url">jdbc:hsqldb:mem:ashish</property>
		<property name="hibernate.hbm2ddl.auto">create</property>

		<!-- EhCache is plugged in to the hibernate.cfg.xml -->
		<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
		
		<!-- enable second level cache and query cache -->
		<property name="hibernate.cache.use_second_level_cache">true</property>
		<property name="hibernate.cache.use_query_cache">true</property>
		<property name="net.sf.ehcache.configurationResourceName">ehCache.xml</property>

		<mapping class="com.ashish.entity.EmployeeEntity"></mapping>
	</session-factory>
</hibernate-configuration>

ehCache.xml: EHCache second level configuration is done in this file


<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
	monitoring="autodetect" dynamicConfig="true">

	<!-- EHCache stores data into memory. In case of over flow of data, it writes 
		into file system -->
	<diskStore path="D:\Ashish\ehcache" />

	<!-- defaultCache is the mandatory configuration. -->
	<defaultCache maxEntriesLocalHeap="10000" eternal="false"
		timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30"
		maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU" statistics="true">
		<!--localTempSwap: Enables temporary local disk usage. This option provides 
			an extra tier for storage during cache operation, but this disk storage is 
			not persisted. After a restart, the disk tier is cleared of any cache data -->
		<persistence strategy="localTempSwap" />
	</defaultCache>

	<!-- cache: defines the region for the second level caching. In the entity class this needs to be annotated -->
	<!-- timeToIdleSeconds: Defines how many seconds the object can be idle 
		in the second level cache -->
	<!-- timeToLiveSeconds: It defines that how many seconds object can be stored 
		in the second level cache whether it is idle or not. -->
	<cache name="com.ashish.entity.EmployeeEntity"
		maxEntriesLocalHeap="100" eternal="false" timeToIdleSeconds="1"
		timeToLiveSeconds="1">
		<persistence strategy="localTempSwap" />
	</cache>

	<cache name="org.hibernate.cache.internal.StandardQueryCache"
		maxEntriesLocalHeap="5" eternal="false" timeToLiveSeconds="120">
		<persistence strategy="localTempSwap" />
	</cache>

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

com.ashish.entity.EmployeeEntity: This is the entity class where the concurrency strategy (Read only, read write, Nonrestricted Read Write, Transactional) is defined.


package com.ashish.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
 
@Entity
@Table(name = "Employee", uniqueConstraints = {@UniqueConstraint(columnNames = "ID"), @UniqueConstraint(columnNames = "EMAIL")})
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="com.ashish.entity.EmployeeEntity")
public class EmployeeEntity implements Serializable
{
   private static final long serialVersionUID = -1798070786993154676L;
   @Id
   @Column(name = "ID", unique = true, nullable = false)
   private Integer           employeeId;
   @Column(name = "EMAIL", unique = true, nullable = false, length = 100)
   private String            email;
   @Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
   private String            firstName;
   @Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
   private String            lastName;
 
   public EmployeeEntity() {}
   
   public EmployeeEntity(int employeeId, String firstName, String lastName, String email) {
	   this.employeeId = employeeId;
	   this.firstName = firstName;
	   this.lastName = lastName;
	   this.email = email;
   }
   public Integer getEmployeeId()
   {
      return employeeId;
   }
 
   public void setEmployeeId(Integer employeeId)
   {
      this.employeeId = employeeId;
   }
 
   public String getEmail()
   {
      return email;
   }
 
   public void setEmail(String email)
   {
      this.email = email;
   }
 
   public String getFirstName()
   {
      return firstName;
   }
 
   public void setFirstName(String firstName)
   {
      this.firstName = firstName;
   }
 
   public String getLastName()
   {
      return lastName;
   }
 
   public void setLastName(String lastName)
   {
      this.lastName = lastName;
   }
}

com.ashish.util.HibernateUtil: This class will read configuration from hibernate.cfg.xml file and returns SessionFactory


package com.ashish.util;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
 
public class HibernateUtil
{
   private static SessionFactory sessionFactory = buildSessionFactory();
 
   private static SessionFactory buildSessionFactory()
   {
      try
      {
         if (sessionFactory == null)
         {
            Configuration configuration = new Configuration().configure(HibernateUtil.class.getResource("/hibernate.cfg.xml"));
            StandardServiceRegistryBuilder serviceRegistryBuilder = new StandardServiceRegistryBuilder();
            serviceRegistryBuilder.applySettings(configuration.getProperties());
            ServiceRegistry serviceRegistry = serviceRegistryBuilder.build();
            sessionFactory = configuration.buildSessionFactory(serviceRegistry);
         }
         return sessionFactory;
      } catch (Throwable ex)
      {
         System.err.println("Initial SessionFactory creation failed." + ex);
         throw new ExceptionInInitializerError(ex);
      }
   }
 
   public static SessionFactory getSessionFactory()
   {
      return sessionFactory;
   }
 
   public static void shutdown()
   {
      getSessionFactory().close();
   }
}

com.ashish.main.MainApp: This class contains the main method and inserts the record of two employees called Ashish, Ujan. Data is retrieved from the database using secong level caching strategy


package com.ashish.main;


import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.stat.Statistics;

import com.ashish.entity.EmployeeEntity;
import com.ashish.util.HibernateUtil;
 
 
public class MainApp
{
	private static SessionFactory sf = null;
	private static Statistics stats = null;
	private static Session session1 = null;
	private static Session session2 = null;
	
	private static Transaction tx1 = null;
	
   public static void main(String[] args)
   {
	  sf = HibernateUtil.getSessionFactory();
	  session1 = sf.openSession();
	  session2 = sf.openSession();;
	  
	  tx1 = session1.beginTransaction();
	  stats = sf.getStatistics();
	  stats.setStatisticsEnabled(true);
      insertRecord();
      tx1.commit();
      
      loadRecords();
      
      sf.close();
   }

	private static void insertRecord() {
		
		// Add new Employee object
	    EmployeeEntity emp = new EmployeeEntity(1, "Ashish", "Mondal", "ashismo@gmail.com");
	    session1.save(emp);
	    
	   // Add another Employee object
	    emp = new EmployeeEntity(2, "Ujan", "Mondal", "ujanmo@gmail.com");
	    session1.save(emp);
	}
	
	private static void loadRecords() {
		printStats("We haven't hit the database so hit=0, miss=0 \n"
				+ "and put=2 because 2 Employee data \n"
				+ "got saved via this session factory");
		
        EmployeeEntity emp = (EmployeeEntity) session1.load(EmployeeEntity.class, 1);
        printRecord(emp, "EmployeeId=1 data is available in session1 so \n"
        		+ "the hit, miss and put count remains same");
        
        emp = (EmployeeEntity) session2.load(EmployeeEntity.class, 1);
        printRecord(emp,  "EmployeeId=1 data is not available in level 1 cache for session2 so \n"
        		+ "the hit, miss and put count remains same");
         
        try {
        	System.out.println("**** Thread is going to sleep for 2 sec so Employee data will get removed from 2nd level cache. \n "
        			+ "Next time it is going to hit database****");
			Thread.sleep(2000);
		} catch (Exception e) {
			e.printStackTrace();
		}
        emp = (EmployeeEntity) session2.load(EmployeeEntity.class, 2);
        printRecord(emp, "EmployeeId=2 data is not available in level 1 and level 2 cache for session2 so \n"
        		+ "the fetch=1, hit=1, miss=1 and put=3");
        
        emp = (EmployeeEntity) session2.get(EmployeeEntity.class, 3); // This data is not available in database
        printRecord(emp, "EmployeeId=3 data is not available in DB so \n"
        		+ "the fetch=1, hit=1, miss=2 and put=3");
	}
	
	private static void printStats(String msg) {
		if(msg != null) {
			System.out.println("=========\n" + msg + "\n===========");
		}
		System.out.println("Fetch Count=" + stats.getEntityFetchCount());
		System.out.println("Second Level Hit Count= " + stats.getSecondLevelCacheHitCount());
		System.out.println("Second Level Miss Count=" + stats.getSecondLevelCacheMissCount());
		System.out.println("Second Level Put Count=" + stats.getSecondLevelCachePutCount());
		System.out.println("---------------------------\n\n");
	}
	private static void printRecord(EmployeeEntity emp, String msg) {
		System.out.println("=========\n" + msg);
		if(emp != null) {
			System.out.println("Data from database - EmpId: " + emp.getEmployeeId() + " Name: " + emp.getFirstName() + " " + emp.getLastName() + " Email: " + emp.getEmail());
		}
		System.out.println("==========");
		printStats(null);
	}
}

Output

Below is the output of the above program. Go through the output and match with the MainApp.loadRecords() method to understand the concept.



blog comments powered by Disqus
J2EE,SOAP,RESTful,SVN,PMD,SONAR,JaCoCo,HTTP,API,MAVEN,AngularJS,GitHub,LDAP,AOP,ORM,JMS,MVC,AWS,SQL,PHP,H2DB,JDBC