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:
- 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).
- If a cached copy of the entity is present in the first-level cache, it is returned as the result of load() method.
- If there is no cached entity in the first-level cache, then the second-level cache is looked up for the cached entity.
- 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.
- 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.
- Second-level cache validates itself for modified entities if the modification has been done through hibernate session APIs.
- 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 toENABLE_SELECTIVE
orDISABLE_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 writetimeToIdealSeconds
,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 !!