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: http://www.terracotta.org/products/enterprise-ehcache
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 !!
can we apply caching for relations like onetone, onetomany … in JPA,if applied what is the behaviour of caching will it work for all relations for read-write strategy?
Thank you for detailed tutorial.
i would like to post one update regarding configuration in hiernate.cfg.xml.
From version 4.x you should use following for hibernate.cache.region.factory_class property.
Thanks for the update. Much appreciated.
Hi,
I have 10 level of inheritance and some of them has-a relation with some entities (with one to one and one to many)
which are lazy loading by default but i want to load eagerly, but for lazy loading query caching is working but for eager loading its hitting db always.kindly reply
I did a quick google search and looks like it is common problem. I am working on it right now.
Hi Lokesh
I have executed the code given by you. I have few questions and issues
System.out.println(HibernateUtil.getSessionfactory().getStatistics().getEntityFetchCount());
System.out.println(HibernateUtil.getSessionfactory().getStatistics().getSecondLevelCacheHitCount());
The above prints 0 and 2 but not 1 and 1
Following are my questions
1. How do i know whether the ehcache is being read and where exactly it is stored(I mean the physical location)
diskStore path=”java.io.tmpdir”. After my program completes i dont see any object at this location.
What i am expecting is what actually is created when we configure ehcache.xml. (Is it any object or any file. What is it )
This is my ehcahe.xml
This is placed in src/maim/resources folder along with hibernate.cfg.xml
Try replacing “java.io.tmpdir” with “c:tempcache” or any such actual physical location. There you should be able to see actual cache files. A similar use case is discussed here: https://howtodoinjava.com/hibernate/hibernate-oscache-configuration-example-tutorial/
Output on first read will be different from repeated reads because entities are read from secondary cache every time after the first read.
The folder specified in diskStore path=”c:\temp\cache” is getting created but i don’t see any objects in that folder.
Once my program execution is completed how do i clear the cache and make sure that next time i run this program the statistics show 1 and 1 instead of 0 and 2
Can you please give me your hibernate.cfg.xml code
And consider using diskPersistent=”true” as well.
Here is my ehcache.xml
Place code as above instructions in red.
Hi Lokesh,
It is very well explained but I still have a query regarding point 7 in How second level cache works? My Question is how application knows that the db is updated directly and it has to invalidate the session.
As I mentioned in point 7 that “there is no way that second level cache update itself“. So application can never know when DB got updated automatically. Either you need to tell application to invalidate it’s whole cache, OR application will do itself when ‘timeToLiveSeconds’ is passed. After this configured time, cache is re-build again whether DB was updated or not. It’s routine process.
So you mean, at least for some time application will not get updated data. Isn’t it a problem which we are overlooking at? Kindly let me know if you find any way we can address this?
Yes, absolutely true. For some time, application will be working with stale data/cache. Unfortunately, there is no way (If any exist then I am not aware of) to notify application/hibernate. Hibernate refresh data only when it is updated through it’s own way. If you use backdoor [direct update in DB] then how hibernate will come to know about this.
Usually to handle such cases, in production applications I have worked on, there was an admin panel where you click “Flush All Cache” button and it will re-generate the whole cache again.
This API would remove all entities for a specific cache region as said here there should be some button to flush out cache entities.
Also, the application which I work on a think wrapper written around DB which sends a notification(model configuration needed for mentioning the table) if any changes happen on the table. Logic is build which receives the event and updates the cache
ehcache or other variants are always work on 2. level cache or is it possible to configure them work on 1. level?Thanks in advance
At first level, there is always hibernate default cache and you cannot even disable it. You can choose anything at only second level cache.
when we set or change entity and commit the transection session cache is updated.So 2. level cache is also updated or not? if so the other sessions will still use old data because they are referring first level cache before second level maybe second level cache changes trigger to all sessions update their data from 2. level context.
Please try to clear your doubt from this article. Everything is explained here. If still have some doubt, please let me know.
Maybe It can help: https://www.ibm.com/support/pages/node/166541
Hello I am just implementing Query level cache in my code using annotation and currently facing a problem ….can you please clarify that do we need to use @Cache() at the entity level if we are implementing Query Level cache only.
No. You don’t need to use @Cache at entity level for implementing Query level cache only.
In attached zip file there is ehCache.xml file is not present?
Look at path: hibernate-test-project / .settings / ehcache.xml
Thanks Lokesh!
I have create a stand alone program for ehcache. I have deleted/renamed ehcache.xml even after that i can see the cached file in java.io.tmpdir” path.
Could you please let me know how/where to use ehcache.xml. Can i rename it/can path updated from java.io.tmpdir to any-other path.
Appreciate your response.
I am applying EHCache for Hibernate4.
This is my configuration. In entity, I configured like this
I have add the jars : ehcache-core-2.4.5.jar,hibernate-ehcache-4.1.0.Final.jar
Problem: For the first click it getting the objects putting into 2nd level cache(ex: SecondLevelCachePutCount -30), from the next click onwards it is getting the 7 objects which are there in the second level cache. Now it is updating SecondLeveCacheMissCount, SecondLevelHitCount (if we observe with JConsole).It should not go to DB again.
Please Help me……
ehcache and OSCache useful in real time projects or not?
Both are powerful to be used in real time projects.
Hi Lokesh,
Can you explain me about stale object in Hibernate?
A stale persistent object is no longer valid. Its fields have default values or values left over from previous transactions and should not be used. An object can also become stale when the transaction in which it was accessed is aborted because of a deadlock or another action that caused AbortException to be signaled.
I will cover more in any future post.
Sorry about my bad English.
Firstly I want to thank you for all these tutorials, secondly, I followed all steps mentioned above but my output was like that :
Output:
would you explain to me why?
Probably you have run the example more than one time. Second level cache store the information in file system and next time when you run the example, it refers from there only. So, first statement will print 0 because it never gone to database. For second statement, it should print 1. Try putting “hibernate.generate_statistics” to true in hibernate.cfg.xml.
Also try inspect values from other methods in : https://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/stat/Statistics.html
yes, After adding “hibernate.generate_statistics” I’ve got the desired result. many thanks.
where should i keep ehcache.xml file
Parallel to “hibernate.cfg.xml”