4
Sponsored Links


Ad by Google
In our previous post we have seen First Level Cache Example. In this post we are going to show you a very simple and real time example of second level cache using annotation.

Second Level Cache: In Hibernate second level cache means Session Factory level cache. Hibernate Session is by default first level cache of persistent data. Sometimes we required to configure a cluster or you can say JVM(Session Factory) level cache. Cache is used to improve the performance of application by reducing your database hit. To configure second level cache, you have to put some additional effort as this is not provided by default. To use second level cache you have to use at least one Cache provider. In our sample application we are going to use one of the popular cache provider called EhCacheProvider. Once you opted the cache provider next you have to use at least one caching strategy, here is the list of Cache Providers and Cache Strategies.

How Second level cache works,

  1. Hibernate received a request to load an entity from the database, Hibernate will check the requested entity into first level cache(Session). If requested entity is found in the first level cache then it will return that entity.
  2. If requested entity is not found in the first level cache then, It will check in the Hibernate second level cache.
  3. If entity is found in second level cache then it will return that entity.
  4. If entity is not found in second level cache, then it will hit the database and return the requested entity from the database.

Lets implement the Second Level Cache, Here is a Country table ER diagram. We are going to use this table in our example.

Tools and Technologies we are using here:

  • JDK 7
  • Hibernate 4.3.7
  • EhCache 2.6.9
  • MySql 5.1.10
  • Eclipse Juno 4.2
  • Maven 3.2
Overview of the Project Structure:

Main Objects of this project are:
  • pom.xml
  • hibernate.cfg.xml
  • ehcache.xml
  • annotated pojo
  • database
Step 1. Create database script.
CREATE DATABASE `hibernate_tutorial`;

USE `hibernate_tutorial`;

/*Table structure for table `country` */

DROP TABLE IF EXISTS `country`;

CREATE TABLE `country` (
  `country_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `country_code` varchar(255) DEFAULT NULL,
  `country_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`country_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

Step 2. Create a Maven Project:
Step A: Go to File->New->Other..


Step B: Select Maven Project from the select wizard.


Step C: Select project name and location from New Maven Project wizard.


Step D: Configure project, provide GroupId, artifactId etc. This screenshot is copied from our previous post, so please change your artifactId and project name something meaning full like SecondLevelCache-Example.

Step 3. Add project dependencies into pom.xml file:
Double click on your project's pom.xml file it will looks like this with very limited information.
<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.javamakeuse</groupId>
  <artifactId>SecondLevelCache-Example</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>SecondLevelCache-Example</name>
  <name>SecondLevelCache Example Using EhCache</name>
  
</project>
Now add Hibernate, mysql and ehcache dependencies entry inside pom.xml file. Below is the code of complete pom.xml file.

pom.xml
<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.javamakeuse</groupId>
  <artifactId>SecondLevelCache-Example</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>SecondLevelCache-Example</name>
  <description>SecondLevelCache Example Using EhCache</description>
  
  <dependencies>
		<!-- Hibernate Dependency -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>4.3.7.Final</version>
		</dependency>
		
		<!-- MySql Connector dependency -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.10</version>
		</dependency>
		<!-- EhCache Core APIs -->
		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache-core</artifactId>
			<version>2.6.9</version>
		</dependency>
		<!-- Hibernate Integration EhCache API -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-ehcache</artifactId>
			<version>4.3.5.Final</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Step 4. Create a hibernate.cfg.xml file and Enable Second Level Cache: In hibernate.cfg.xml we enabled the second level cache and also provided the cache provider details.

Place hibernate.cfg.xml file inside src/main/resources folder.
hibernate.cfg.xml
<?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="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/hibernate_tutorial</property>
        <property name="connection.username">root</property>
        <property name="connection.password">root</property>
         
        <property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
        
         <!-- Enable Second Level Cache -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <property name="net.sf.ehcache.configurationResourceName">ehcache.xml</property>
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
 
        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="hbm2ddl.auto">update</property>
        <mapping class="com.javamakeuse.poc.pojo.Country"/>
          
    </session-factory>
</hibernate-configuration>

Step 5. Create ehcache.xml, In ehcache.xml we provided ehcache policies such as disk location of temp file, cache name, max entries, eviction policy and lot please see the entries very meaning full properties are defined in ehcache.xml.

ehcache.xml, Place inside src/main/resources folder
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>

	<diskStore path="F:/tmp" />

	<defaultCache maxEntriesLocalHeap="4000" eternal="false"
		timeToIdleSeconds="60" timeToLiveSeconds="120" diskSpoolBufferSizeMB="20"
		maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU" statistics="true">
		<persistence strategy="localTempSwap" />
	</defaultCache>

	<cache name="country" maxEntriesLocalHeap="4000" eternal="false"
		timeToIdleSeconds="5" timeToLiveSeconds="10">
		<persistence strategy="localTempSwap" />
	</cache>

	<cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
		maxEntriesLocalHeap="5000" eternal="true">
		<persistence strategy="localTempSwap" />
	</cache>
</ehcache>
Step 6. Create Country annotated class: In country class we declared Read Only Cache strategy using @Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region = "country")

Country.java
package com.javamakeuse.poc.pojo;

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 = "country")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region = "country")
public class Country {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "country_id")
	private long countryID;

	@Column(name = "country_name")
	private String countryName;

	@Column(name = "country_code")
	private String countryCode;

	public Country() {
	}

	public Country(String countryName, String countryCode) {
		super();
		this.countryName = countryName;
		this.countryCode = countryCode;
	}

	public String getCountryName() {
		return countryName;
	}

	public String getCountryCode() {
		return countryCode;
	}

	@Override
	public String toString() {
		return "Country [countryID=" + countryID + ", countryName="
				+ countryName + ", countryCode=" + countryCode + "]";
	}

}
Step 7. Create a HibernateUtility class: HibernateUtility class to build SessionFactory via loading Configuration file.

HibernateUtility .java
package com.javamakeuse.poc.util;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;

public class HibernateUtility {
  private static final SessionFactory sessionFactory = buildSessionFactory();
     private static SessionFactory buildSessionFactory() {
     
      Configuration configuration = new Configuration();
      configuration.configure();

      ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().
      applySettings(configuration.getProperties()).buildServiceRegistry();
      SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
   return sessionFactory;
     }
   
     public static SessionFactory getSessionFactory() {
         return sessionFactory;
     }
}
Step 8. Create Main.java class, In our Main service class we trying to load same entity four times from the database, But exactly only one sql query statement will be executed, and three times entity will loaded from the second level cache only instead of database hit. Even if we try to load the same entity in another Session, Hibernate will load the entity from the second level cache only because second level cache is not limited to Session but it's Session Factory level. Please see the out put to get it more clearly.

Main.java
package com.javamakeuse.poc.service;

import org.hibernate.Session;

import com.javamakeuse.poc.pojo.Country;
import com.javamakeuse.poc.util.HibernateUtility;

public class Main {
	public static void main(String[] args) {
		Session session = HibernateUtility.getSessionFactory().openSession();

		session.beginTransaction();

		Country country = null;

		country = (Country) session.load(Country.class, 1l);
		
		System.out.println("Country from the Database => "+country);
		System.out.println();

		System.out.println("Going to print Country *** from First Level Cache");
		// second time loading same entity from the first level cache
		country = (Country) session.load(Country.class, 1l);
		System.out.println(country);
		
		// removing country object from the first level cache.
		session.evict(country);
		System.out.println("Object removed from the First Level Cache");
		System.out.println();
		System.out
				.println("Going to print Country *** from Second level Cache");
		country = (Country) session.load(Country.class, 1l);
		System.out.println(country);
		session.getTransaction().commit();

		// loading object in another session
		Session session2 = HibernateUtility.getSessionFactory().openSession();
		session2.beginTransaction();
		System.out.println();
		System.out
				.println("Printing Country *** from Second level Cache in another session");
		country = (Country) session2.load(Country.class, 1l);
		System.out.println(country);
		session2.getTransaction().commit();

	}
}

OUT PUT
Hibernate: 
    select
        country0_.country_id as country_1_0_0_,
        country0_.country_code as country_2_0_0_,
        country0_.country_name as country_3_0_0_ 
    from
        country country0_ 
    where
        country0_.country_id=?

Country from the Database => Country [countryID=1, countryName=Australiya, countryCode=+61]

Going to print Country *** from First Level Cache
Country [countryID=1, countryName=Australiya, countryCode=+61]
Object removed from the First Level Cache

Going to print Country *** from Second level Cache
Country [countryID=1, countryName=Australiya, countryCode=+61]

Printing Country *** from Second level Cache in another session
Country [countryID=1, countryName=Australiya, countryCode=+61]

Download the complete example from here Source Code


References:
Reference 1
Reference 2
Sponsored Links

4 comments:

  1. Thank you very much, it was the first complete and working snd level cache sample I found over the internet.

    ReplyDelete
  2. Glad to hear that you liked it, keep visiting definitely you will find something interesting for you, or if you have something to add you can also share your experience

    ReplyDelete
  3. Thank you for the post. But, when do we need second level cache data, when data is already provided by first level? I'm very confused with this one.

    ReplyDelete
  4. @Nizam, Hibernate already do the first level of cache without making any extra config. But for second level cache, you need to do some config/coding. And If data is not available in first level cache than only, it will asked for second level, if configured. Sometimes you need frequently access data to be in second level, so that you can reduce the database hit.

    ReplyDelete