Second level EHCache example in Hibernate 5

Introduction

This tutorial will show second level ehcache example in Hibernate 5. EH stands for Easy Hibernate. We know that there are three types of caching mechanism such as First Level – session, Second Level – SessionFactory and Query Level – SessionFactory. Caching mechanism improves the performance of application because the data are loaded from cache instead of hitting the database again.

For more information on First Level, Second Level and Query Level please go through Hibernate Caching strategy.

Prerequisites

MySQL Database 8.0.17, JDK 1.8, Hibernate 5.2.17, Eclipse Neon

Let’s see how we can configure second level ehcache example in Hibernate.

Creating MySQL Table

We will use here MySQL database. Therefore we will create a table called cd under roytuts database in MySQL server.

CREATE TABLE `cd` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(100) COLLATE latin1_general_ci NOT NULL,
  `artist` varchar(100) COLLATE latin1_general_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

Dumping Some Data

Dumping data is not required but to test the application quickly after implementation we need some data.

insert  into `cd`(`id`,`title`,`artist`) values
(1,'CD Caching','Soumitra'),
(2,'CD Caching','Soumitra'),
(3,'CD Caching','Soumitra'),
(4,'CD Caching','Soumitra');

Creating Project

Create a gradle based project in Eclipse with project name as hibernate-second-level-ehcache.

Updating Build Script

Default build.gradle script does not include the required dependencies, so we will add required dependencies.

We have added hibernate, ehcache and mysql dependencies. We have also added loggers API but commented out because we will control logger using Java code. But if you want to control logger using log4j.properties file then you need these API.

apply plugin: 'java'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
	mavenLocal()
    mavenCentral()
}
dependencies {
	implementation("org.hibernate:hibernate-core:5.2.17.Final")
	implementation("org.hibernate:hibernate-ehcache:5.2.17.Final")
	implementation("net.sf.ehcache:ehcache-core:2.6.11")
	implementation("mysql:mysql-connector-java:8.0.17")
	//implementation("log4j:log4j:1.2.16")
	//implementation("org.slf4j:slf4j-log4j12:1.6.4")
}

Creating Entity Class

We will create entity class that will map database table to our Java class.

The below POJO class file is simple and easy to use. It just declares the attributes same as in the database table.

We have not annotated class attributes with @Column because we have the same column names in table.

To enable using caching for the entity class we have used @Cache with caching strategy.

You can find different definition of cache strategies here.

package com.roytuts.hibernate.second.level.ehcache.entity;
import java.io.Serializable;
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 = "cd")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Cd implements Serializable {
	private static final long serialVersionUID = 1L;
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String title;
	private String artist;
	// getters and setters
}

Hibernate Configuration File

Now we need to configure hibernate to connect the database table as well as to use the second level cache for ehcache. Below is the hibernate.cfg.xml file under src/main/resources folder.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 5.2.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-5.2.0.dtd">
<hibernate-configuration>
	<session-factory>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
		<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/roytuts?zeroDateTimeBehavior=convertToNull</property>
		<property name="hibernate.connection.username">root</property>
		<property name="hibernate.connection.password">root</property>
		<property name="hibernate.current_session_context_class">thread</property>
		<property name="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</property>
		<property name="hibernate.show_sql">true</property>
		<property name="hibernate.format_sql">true</property>
		<property name="hibernate.generate_statistics">true</property>
		<property name="hibernate.cache.use_structured_entries">true</property>
		<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>
		<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</property>
		<mapping
			class="com.roytuts.hibernate.second.level.ehcache.entity.Cd" />
	</session-factory>
</hibernate-configuration>

I will explain each of the property tag here:

Below property denotes what type of Dialect should be used for the database.

<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

Below property denotes which driver class should be used based on the database.

<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

Below is the full database URL with host, port and database instance. zeroDateTimeBehavior=convertToNull will convert ‘0000-00-00 00:00:00’ or ‘0000-00-00’ to null value.

<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/cdcol?zeroDateTimeBehavior=convertToNull</property>

Below denotes username of the database.

<property name="hibernate.connection.username">root</property>

Below denotes password for the database. I don’t have password for my database so I didn’t use this property.

<property name="hibernate.connection.password">root</property>

Which session context we should use. Possible values are jta, thread and managed. I use thread.

<property name="hibernate.current_session_context_class">thread</property>

Below is the query parser for performing the translation.

<property name="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</property>

Show generated sql in the console

<property name="hibernate.show_sql">true</property>

Format the generate sql query in the console for better readability

<property name="hibernate.format_sql">true</property>

Hibernate gives some useful metrics like Entity Insert/Delete/Load/Fetch count, Query Cache hit/miss/put/get count etc.

<property name="hibernate.generate_statistics">true</property>

Force Hibernate to keep the cache entries in a more readable format.

<property name="hibernate.cache.use_structured_entries">true</property>

Use below property to use the second level cache in Hibernate.

<property name="hibernate.cache.use_second_level_cache">true</property>

Below property denotes that we are using second level cache for EhCache.

<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</property>

This is the mapping class with the database table.

<mapping
			class="com.roytuts.hibernate.second.level.ehcache.entity.Cd" />

Below property indicates if the ehcache.xml file located in other than classpath location.

<property name="net.sf.ehcache.configurationResourceName">/ehcache.xml</property>

EHCache Configuration

Nest step is to configure the ehcache.xml file and put it under classpath, i.e., in the src/main/resources folder.

<?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">
	<diskStore path="java.io.tmpdir/ehcache" />
	<defaultCache maxElementsInMemory="1000" eternal="false"
		timeToIdleSeconds="3" timeToLiveSeconds="120" overflowToDisk="true" />
	<cache
		name="com.roytuts.hibernate.second.level.ehcache.entity.Cd"
		maxElementsInMemory="100" eternal="false" timeToIdleSeconds="3"
		timeToLiveSeconds="120" overflowToDisk="false" />
</ehcache>

In this file there are few things to mention –

diskStore: EhCache stores data into memory but when it starts overflowing, it start writing data into file system. We use “path” property to define the location where EhCache will write the overflown data.

defaultCache: It’s a mandatory configuration, it is used when an object need to be cached and there are no caching regions defined for that.

cache name=”in.webtuts.domain.CD”: We use cache element to define the region and its configurations. We can define multiple regions and their properties. While we define model beans cache properties, we can also define region with caching strategies.

In ehcache.xml if eternal=”true” then we should not write timeToIdealSeconds, timeToLiveSeconds because Hibernate will take automatically care about those values. So if you want to give values for timeToIdealSeconds, timeToLiveSeconds then use better eternal=”false” always.

In ehcache.xml timeToIdealSeconds=”seconds” means, if the object in the global chche is idle for sometime, means not using by any other class or object then it will be waited for the time we have specified in timeToIdealSeconds and it will be deleted from the global cache if time is exceeds more than timeToIdealSeconds value.

In ehcache.xml timeToLiveSeconds=”seconds” means the other Session or class using this object or not, whatever the situation occurrs, when the time exceeds timeToLiveSeconds value then it will be removed from the global cache by hibernate.

Creating SessionFactory

Now we will create HibernateUtil.java file which will give us the Singleton SessionFactory.

package com.roytuts.hibernate.second.level.ehcache.util;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
public class HibernateUtil {
	public static SessionFactory getSessionFactory() {
		try {
			StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
					.configure("hibernate.cfg.xml").build();
			Metadata metadata = new MetadataSources(standardRegistry).getMetadataBuilder().build();
			return metadata.getSessionFactoryBuilder().build();
		} catch (Throwable ex) {
			// Make sure you log the exception, as it might be swallowed
			System.err.println("Initial SessionFactory creation failed." + ex);
			throw new ExceptionInInitializerError(ex);
		}
	}
	public static void closeSession() {
		// Close caches and connection pools
		getSessionFactory().close();
	}
}

Creating Main Class

Next we write the EhCacheApp.java for testing the application.

package com.roytuts.hibernate.second.level.ehcache;
import java.util.logging.Level;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.stat.Statistics;
import com.roytuts.hibernate.second.level.ehcache.entity.Cd;
import com.roytuts.hibernate.second.level.ehcache.util.HibernateUtil;
public class EhCacheApp {
	public static void main(String[] args) {
		java.util.logging.Logger.getLogger("org.hibernate").setLevel(Level.SEVERE);
		Session sess = null;
		Session dbSession1 = null;
		Session dbSession2 = null;
		Session newSession = null;
		Transaction tran = null;
		Transaction dbTransaction1 = null;
		Transaction dbTransaction2 = null;
		Transaction newTransaction = null;
		SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
		Statistics statistics = sessionFactory.getStatistics();
		System.out.println("Fetch count from DB: " + statistics.getEntityFetchCount());
		System.out.println("Fetch count from second-level session: " + statistics.getSecondLevelCacheHitCount());
		System.out.println("Miss count from second-level session: " + statistics.getSecondLevelCacheMissCount());
		System.out.println("Put count from second-level session:" + statistics.getSecondLevelCachePutCount());
		System.out.println();
		try {
			// save the new CD
			sess = sessionFactory.openSession();
			tran = sess.beginTransaction();
			Cd cd = new Cd();
			cd.setTitle("CD Caching");
			cd.setArtist("Soumitra");
			sess.save(cd);
			tran.commit();
			sess.close();
			dbSession1 = sessionFactory.openSession();
			dbTransaction1 = dbSession1.beginTransaction();
			// load the CD from database table
			Cd cd1 = (Cd) dbSession1.load(Cd.class, 4l);
			System.out.println("Cd Loaded from DB - title: " + cd1.getTitle());
			System.out.println();
			// load the CD from first-level Session
			Cd cd2 = (Cd) dbSession1.load(Cd.class, 4l);
			System.out.println("Cd Loaded from First-level session - title: " + cd2.getTitle());
			System.out.println();
			dbTransaction1.commit();
			dbSession1.close();
			System.out.println("Fetch count from DB: " + statistics.getEntityFetchCount());
			System.out.println("Fetch count from second-level session: " + statistics.getSecondLevelCacheHitCount());
			System.out.println("Miss count from second-level session: " + statistics.getSecondLevelCacheMissCount());
			System.out.println("Put count from second-level session:" + statistics.getSecondLevelCachePutCount());
			System.out.println();
			System.out.println("Waiting........");
			System.out.println();
			Thread.sleep(5000);
			dbSession2 = sessionFactory.openSession();
			dbTransaction2 = dbSession2.beginTransaction();
			// load the Cd from Database
			Cd cd3 = (Cd) dbSession2.load(Cd.class, 4l);
			System.out.println();
			System.out.println("Again Loaded Cd from DB - title: " + cd3.getTitle());
			System.out.println();
			dbTransaction2.commit();
			dbSession2.close();
			System.out.println("Fetch count from DB: " + statistics.getEntityFetchCount());
			System.out.println("Fetch count from second-level session: " + statistics.getSecondLevelCacheHitCount());
			System.out.println("Miss count from second-level session: " + statistics.getSecondLevelCacheMissCount());
			System.out.println("Put count from second-level session:" + statistics.getSecondLevelCachePutCount());
			System.out.println();
			newSession = sessionFactory.openSession();
			newTransaction = newSession.beginTransaction();
			// we have already second-level of cache for the Cd
			Cd cd4 = (Cd) newSession.get(Cd.class, 4l);
			System.out.println("Cd loaded from Second-level - title: " + cd4.getTitle());
			System.out.println();
			newTransaction.commit();
			newSession.close();
			System.out.println("Fetch count from DB: " + statistics.getEntityFetchCount());
			System.out.println("Fetch count from second-level session: " + statistics.getSecondLevelCacheHitCount());
			System.out.println("Miss count from second-level session: " + statistics.getSecondLevelCacheMissCount());
			System.out.println("Put count from second-level session:" + statistics.getSecondLevelCachePutCount());
			System.out.println();
		} catch (Exception e) {
			e.printStackTrace();
			if (tran != null && sess.isOpen()) {
				tran.rollback();
			}
			if (dbTransaction1 != null && dbSession1.isOpen()) {
				dbTransaction1.rollback();
			}
			if (dbTransaction2 != null && dbSession2.isOpen()) {
				dbTransaction2.rollback();
			}
			if (newTransaction != null && newSession.isOpen()) {
				newTransaction.rollback();
			}
		} finally {
			HibernateUtil.closeSession();
		}
	}
}

Note that for a cached query, the cache miss is equals to the db count.

Testing the Application

Once you run the EhCacheApp.java then you will see the following output:

Fetch count from DB: 0
Fetch count from second-level session: 0
Miss count from second-level session: 0
Put count from second-level session:0
Hibernate:
    insert
    into
        cd
        (artist, title)
    values
        (?, ?)
Hibernate:
    select
        cd0_.id as id1_0_0_,
        cd0_.artist as artist2_0_0_,
        cd0_.title as title3_0_0_
    from
        cd cd0_
    where
        cd0_.id=?
Cd Loaded from DB - title: CD Caching
Cd Loaded from First-level session - title: CD Caching
Fetch count from DB: 1
Fetch count from second-level session: 0
Miss count from second-level session: 1
Put count from second-level session:1
Waiting........
Hibernate:
    select
        cd0_.id as id1_0_0_,
        cd0_.artist as artist2_0_0_,
        cd0_.title as title3_0_0_
    from
        cd cd0_
    where
        cd0_.id=?
Again Loaded Cd from DB - title: CD Caching
Fetch count from DB: 2
Fetch count from second-level session: 0
Miss count from second-level session: 2
Put count from second-level session:2
Cd loaded from Second-level - title: CD Caching
Fetch count from DB: 2
Fetch count from second-level session: 1
Miss count from second-level session: 2
Put count from second-level session:2

Explanation of the above output

1. First we get the below statistics and all are 0 because we don’t have any Session, Transaction etc.

Fetch count from DB: 0
Fetch count from second-level session: 0
Miss count from second-level session: 0
Put count from second-level session:0

2. Then we save one CD entity because we have to test with this entity. We open the dbSession1 and start the transaction dbTransaction1. Then we load cd1 entity from the database as the database has been hit for loading the data.

Hibernate:
    insert
    into
        cd
        (artist, title)
    values
        (?, ?)

cd1 entity is loaded from database as seen from the output.

Cd Loaded from DB - title: CD Caching
Hibernate:
    select
        cd0_.id as id1_0_0_,
        cd0_.artist as artist2_0_0_,
        cd0_.title as title3_0_0_
    from
        cd cd0_
    where
        cd0_.id=?

cd2 entity is loaded from the first level session because in this case database was not hit and session was also not closed while fetching the data as it is clear from the below output.

Cd Loaded from First-level session - title: CD Caching

So here we get the below statistics:

Fetch count from DB: 1
Fetch count from second-level session: 0
Miss count from second-level session: 1
Put count from second-level session:1

All have obvious meaning. Note that the cache miss is equal to the db count. Put count is the count of storing the data in cache.

Whenever the data is stored in first level then the data is also stored in the second level cache.

Note we need to close the Session after each transaction for getting the obvious output.

3. If you look at ehcahe.xml we have timeToIdleSeconds="3" so, it means after 3 idle seconds the session gets expired.

Now if you look carefully at EhCacheApp.java file then you will find Thread.sleep(5000). So we are here waiting for 5 seconds and it is obvious that the second level cache must expire by this time. So we get the below output after Waiting…….. for cd3 entity.

Hibernate:
    select
        cd0_.id as id1_0_0_,
        cd0_.artist as artist2_0_0_,
        cd0_.title as title3_0_0_
    from
        cd cd0_
    where
        cd0_.id=?
Again Loaded Cd from DB - title: CD Caching
Fetch count from DB: 2
Fetch count from second-level session: 0
Miss count from second-level session: 2
Put count from second-level session:2

So the above output is obvious and db count, miss count, put count have been increased by 1.

4. Now we load the cd4 entity and get the below output:

Cd loaded from Second-level - title: CD Caching
Fetch count from DB: 2
Fetch count from second-level session: 1
Miss count from second-level session: 2
Put count from second-level session:2

In the above output db count, miss count and put count are same but fetch count has been increased by 1 because we have got this entity from the second level cache.

How do we know that it is coming from second level cache ?

We had opened new session and started new transaction for fetching this cd4 entity.

Source Code

download source code

Thanks for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *