Hibernate EhCache Configuration Tutorial

Learn to configure and use the Ehcache library as the second-level cache provider for the hibernate application. This tutorial works with Hibernate 5.x and Ehcache 2.x version.

1. Second-level Cache – A Quick Recap

We have already learned about the role of the second-level cache in hibernate performance. Let’s quickly go through the basics to begin:

  1. Whenever hibernate Session tries to load an entity, the very first place it looks for a cached copy of the entity in first-level cache (associated with a particular hibernate Session).
  2. If a cached copy of the entity is present in the first-level cache, it is returned as the result of load() method.
  3. If there is no cached entity in the first-level cache, then the second-level cache is looked up for the cached entity.
  4. If second-level cache has the cached entity, it is returned as the result of load() method. But, before returning the entity, it is stored in first level cache also so that the next invocation to load() method for that entity will return the entity from the first level cache itself, and there will not be a need to go to the second level cache again.
  5. If the entity is not found in first level cache and second level cache also, then a database query is executed and the entity is stored in both cache levels, before returning as the response to load() method.
  6. Second-level cache validates itself for modified entities if the modification has been done through hibernate session APIs.
  7. If some user or process makes changes directly in the database, there is no way that the second-level cache update itself until “timeToLiveSeconds” duration has passed for that cache region. In this case, it is a good idea to invalidate the whole cache and let hibernate build its cache once again. You can use sessionFactory.evictEntity() in a loop to invalidate the whole Hibernate second-level cache.

2. EhCache Library

Terracotta Ehcache is a popular open-source Java cache that can be used as a Hibernate second-level cache. It can be used as a standalone second-level cache or can be configured for clustering to provide a replicated coherent second-level cache.

Hibernate ships with the ehcache library. If you want any particular version of ehcache, visit the Terracotta Ehcache download site: https://www.ehcache.org/

Alternatively, we can include the latest version of ehcache from the Maven site:

<dependency>
  <groupId>net.sf.ehcache</groupId>
  <artifactId>ehcache</artifactId>
  <version>[2.0.0]</version>
</dependency>

3. Enabling EhCache

To configure ehcache, we need to do two steps:

  • Configure Hibernate to enable second-level caching
  • Specify the second-level cache provider as ehcache

3.1. Hibernate 4.x and 5.x

<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

3.2 Hibernate 3.3 and Above

<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</property>

3.3. Hibernate 3.2 and Below

<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</property>

4. Making the Entities Cacheable

To make an entity cacheable, there are two ways.

  • If you are using hbm.xml files then use below configuration:
<class name="com.application.entity.DepartmentEntity" table="...">
    <cache usage="read-write"/>
</class>
  • Otherwise, if you are using annotations, use either @Cache or @Cacheable annotation. Note that @Cache is the Hibernate cache interface and @Cacheable is the JPA cache interface.
    @Cacheable will only work if the caching element(persistence.xml) is set to ENABLE_SELECTIVE or DISABLE_SELECTIVE.
@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="department")
public class DepartmentEntity implements Serializable 
{
  //code
}

For both types of configurations, the caching strategy can be of the following types:

  • none: No caching will happen.
  • read-only: If your application needs to read, but not modify, instances of a persistent class, a read-only cache can be used.
  • read-write: If the application needs to update data, a read-write cache might be appropriate.
  • nonstrict-read-write: If the application only occasionally needs to update data (i.e. if it is extremely unlikely that two transactions would try to update the same item simultaneously), and strict transaction isolation is not required, a nonstrict-read-write cache might be appropriate.
  • transactional: The transactional cache strategy provides support for fully transactional cache providers such as JBoss TreeCache. Such a cache can only be used in a JTA environment and you must specify hibernate.transaction.manager_lookup_class.

5. Enabling Query Cache

We can also enable query caching. To do so configure it in the hbm.xml:

<property key="hibernate.cache.use_query_cache">true</property>

and where queries are defined in the code, add the method call setCacheable(true) to the queries that should be cached:

sessionFactory.getCurrentSession().createQuery("...").setCacheable(true).list();

By default, Ehcache will create separate cache regions for each entity that we configure for caching. We can change the defaults for these regions by adding the configuration to your ehcache.xml. To provide this configuration file, use this property in hibernate configuration:

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

And use the below configuration to override the default configuration:

<cache
    name="com.somecompany.someproject.domain.Country"
    maxElementsInMemory="10000"
    eternal="false"
    timeToIdleSeconds="300"
    timeToLiveSeconds="600"
    overflowToDisk="true"
/>
  • Please note that in ehcache.xml, if eternal attribute is set to true then we should not write timeToIdealSeconds, timeToLiveSeconds, hibernate will take care of these values.
  • So if you want to give values manually better use eternal="false" always, so that we can assign values into timeToIdealSeconds, timeToLiveSeconds manually.
  • timeToIdealSeconds=”seconds” means, if the object in the global cache is ideal, means not used by any other class or object then it will wait for the time we specified and get deleted from the global cache if time exceeds more than timeToIdealSeconds value.
  • timeToLiveSeconds=”seconds” means, the other Session or class using this object or not, I mean maybe it is using by other sessions or may not, whatever the situation might be, once it competed the time specified timeToLiveSeconds, then it will be removed from the global cache by hibernate.

6. Demo

In our example application, I have one DepartmentEntity for which I want to enable second-level cache using Ehcache. Let’s record the changes step by step:

6.1. 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="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernatedemo</property>
        <property name="hibernate.connection.password">password</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
    <property name="hbm2ddl.auto">create</property>
    <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
        <mapping class="hibernate.test.dto.DepartmentEntity"></mapping>
    </session-factory>
</hibernate-configuration>

6.2. DepartmentEntity.java

import java.io.Serializable;
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 javax.persistence.UniqueConstraint;
 
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
 
@Entity (name = "dept")
@Table(name = "DEPARTMENT", uniqueConstraints = {
    @UniqueConstraint(columnNames = "ID"),
    @UniqueConstraint(columnNames = "NAME") })
     
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="department")
 
public class DepartmentEntity implements Serializable {
   
  private static final long serialVersionUID = 1L;
 
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID", unique = true, nullable = false)
  private Integer id;
   
  @Column(name = "NAME", unique = true, nullable = false, length = 100)
  private String name;
 
  public Integer getId() {
    return id;
  }
 
  public void setId(Integer id) {
    this.id = id;
  }
 
  public String getName() {
    return name;
  }
 
  public void setName(String name) {
    this.name = name;
  }
}

6.3. HibernateUtil.java

import java.io.File;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
 
public class HibernateUtil 
{
  private static final SessionFactory sessionFactory = buildSessionFactory();
    
    private static SessionFactory buildSessionFactory() 
    {
        try
        {
            // Create the SessionFactory from hibernate.cfg.xml
            return new AnnotationConfiguration().configure(new File("hibernate.cgf.xml")).buildSessionFactory();
        }
        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 SessionFactory getSessionFactory() {
        return sessionFactory;
    }
  
    public static void shutdown() {
      // Close caches and connection pools
      getSessionFactory().close();
    }
}

6.4. TestHibernateEhcache.java

public class TestHibernateEhcache 
{ 
  public static void main(String[] args) 
  {
    storeData();
     
    try
    {
      //Open the hibernate session
      Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();
       
      //fetch the department entity from database first time
      DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
      System.out.println(department.getName());
       
      //fetch the department entity again; Fetched from first level cache
      department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
      System.out.println(department.getName());
       
      //Let's close the session
      session.getTransaction().commit();
      session.close();
       
      //Try to get department in new session
      Session anotherSession = HibernateUtil.getSessionFactory().openSession();
      anotherSession.beginTransaction();
       
      //Here entity is already in second level cache so no database query will be hit
      department = (DepartmentEntity) anotherSession.load(DepartmentEntity.class, new Integer(1));
      System.out.println(department.getName());
       
      anotherSession.getTransaction().commit();
      anotherSession.close();
    }
    finally
    {
      System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount()); //Prints 1
      System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount()); //Prints 1
       
      HibernateUtil.shutdown();
    }
  }
   
  private static void storeData()
  {
    Session session = HibernateUtil.getSessionFactory().openSession();
    session.beginTransaction();
     
    DepartmentEntity department = new DepartmentEntity();
    department.setName("Human Resource");
     
    session.save(department);
    session.getTransaction().commit();
  }
}
Hibernate: insert into DEPARTMENT (NAME) values (?)
Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource
Human Resource
Human Resource
1
1

In the above output, the first time the department is fetched from the database. but the next two times it is fetched from cache. The last fetch is from the second-level cache.

Happy Learning !!

Comments

Subscribe
Notify of
guest
38 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode