Welcome to the Hibernate Second Level Cache Example tutorial. Now, we will taking a ehcache configuration example to clarify about Hibernate Second Level Cache. EHCache is the most popular Hibernate Second Level Cache provider.
One important thing, we should know different strategies for caching an object:
Read Only: It should be used for persistent objects that will be always read but never updated. The read-only strategy is the simplest and with best performance because no need to check if an object is updated in database or not.
Read Write: This strategy is suitable for the hibernate application whose data is updated. Just a exception, the data is updated in database through other applications and Hibernate is out of control in this case. So while using this strategy, we have to make sure our application and other applications are using Hibernate API for updating the data.
Nonrestricted Read Write: If the application rarely needs to update data and strict transaction isolation is not required, we may use the nonstrict–read–write strategy. It has less overhead than read-write.
Transactional: We can use this strategy if we need a fully transactional cache. However, it is only suitable in a JTA environment and we must specify hibernate.transaction.manager_lookup_class.
EHCache is the best choice when we are looking for second level cache in hibernate. EHCache supports all the above cache strategies. We will create examples to know how EHCache works for hibernate application.
Other interesting posts you may like
Create project directory structure
In this Hibernate Second Level Cache Example, we are creating a maven project in the Eclipse, final project directory structure will look like below image
Maven Dependencies
We would provide ehcache-core and hibernate-ehcache dependencies in pom.xml file. Beside EHCache uses slf4j for logging, so we also add slf4j-simple dependencies in pom.xml file. Of course, don’t forget add hibernate-core and mysql-connector-java dependencies.
Our pom.xml like below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<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.javabycode.hibernate</groupId> <artifactId>HibernateEHCacheExample</artifactId> <version>0.0.1-SNAPSHOT</version> <description>Hibernate Secondary Level Cache Example using EHCache implementation</description> <dependencies> <!-- Hibernate Core API --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>4.3.11.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.5.6-Final</version> </dependency> <!-- MySQL Driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.0.5</version> </dependency> <!-- EHCache Core APIs --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.11</version> </dependency> <!-- Hibernate EHCache API --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-ehcache</artifactId> <version>4.3.11.Final</version> </dependency> <!-- EHCache uses slf4j for logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.5</version> </dependency> </dependencies> </project> |
Hibernate Configuration file
As you know Hibernate Second level cache is disabled by default, so we enable it and add some configurations like below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration SYSTEM "classpath://org/hibernate/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.username">javabycode</property> <property name="hibernate.connection.password">mypassword</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/javabycode</property> <property name="format_sql">false</property> <property name="hibernate.show_sql">true</property> <property name="hibernate.current_session_context_class">thread</property> <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property> <!-- enable second level cache and query cache --> <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">/myehcache.xml</property> <mapping class="com.javabycode.hibernate.model.Student" /> </session-factory> </hibernate-configuration> |
Let’s dig deeper:
The hibernate.cache.region.factory_class property is used to define Factory class that we use for Second level cache. In this case, we are using org.hibernate.cache.ehcache.EhCacheRegionFactory. Or using org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory class when we want the factory class to be singleton.
The hibernate.cache.use_second_level_cache property is used to enable the second level cache in hibernate application.
The hibernate.cache.use_query_cache property is used to enable the query cache, so that HQL queries results will be cached.
The net.sf.ehcache.configurationResourceName property is used to define location of EHCache configuration file. This property is an optional and if it’s not set EHCache will try to find it in the application classpath.
Hibernate EHCache Configuration File
Our EHCache configuration file looks like below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?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 maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" statistics="true"> <persistence strategy="localTempSwap" /> </defaultCache> <cache name="employee" maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="10"> <persistence strategy="localTempSwap" /> </cache> <cache name="org.hibernate.cache.internal.StandardQueryCache" maxEntriesLocalHeap="5" eternal="false" timeToLiveSeconds="120"> <persistence strategy="localTempSwap" /> </cache> <cache name="org.hibernate.cache.spi.UpdateTimestampsCache" maxEntriesLocalHeap="5000" eternal="true"> <persistence strategy="localTempSwap" /> </cache> </ehcache> |
Hibernate EHCache provides a lot of options, I won’t go into much detail but some of the important configurations above are:
diskStore: EHCache stores data into memory by default. However, it will start to writing data into file system on disk when the memory is overflowed. This property defines the location where EHCache can write on disk.
defaultCache: This is a mandatory configuration, if there is no caching region defined the defaultCache will be used.
cache name=”student”: we can define the cache region by using the cache property. We can define multiple cache regions with caching strategies.
Cache regions org.hibernate.cache.internal.StandardQueryCache and org.hibernate.cache.spi.UpdateTimestampsCache is used for Query Cache. The org.hibernate.cache.internal.StandardQueryCache region will holds the cached query results. The org.hibernate.cache.spi.UpdateTimestampsCache region will holds timestamps of the most recent updates to queryable tables. These timestamps validate results served from the query cache.
You can refer to the ehcache.xml via this link
Model Bean Caching Strategy
We create a Model bean like below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
package com.javabycode.hibernate.model; import java.io.Serializable; import java.util.Date; 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 = "STUDENT") @Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="student") public class Student implements Serializable { private static final long serialVersionUID = 6832006422622219737L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "NAME", nullable = false) private String name; @Column(name = "ENTERING_DATE", nullable = false) private Date enteringDate; @Column(name = "NATIONALITY", nullable = false) private String nationality; @Column(name = "CODE", nullable = false) private String code; public Student(){ } public Student(String name, Date enteringDate,String nationality, String code){ this.name = name; this.enteringDate = enteringDate; this.nationality = nationality; this.code = code; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void setCode(String code) { this.code = code; } public void setEnteringDate(Date enteringDate) { this.enteringDate = enteringDate; } public void setNationality(String nationality) { this.nationality = nationality; } public String getCode() { return code; } public Date getEnteringDate() { return enteringDate; } public String getNationality() { return nationality; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + name.hashCode(); result = prime * result + ((code == null) ? 0 : code.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof Student)) return false; Student other = (Student) obj; if (id != other.id) return false; if (code == null) { if (other.code != null) return false; } else if (!code.equals(other.code)) return false; return true; } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", enteringDate=" + enteringDate + ", nationality=" + nationality + ", code=" + code + "]"; } } |
Let’s dig deeper:
@Cache annotation to enable the caching configuration.
The attribute CacheConcurrencyStrategy is used to define the caching strategy
The attribute region is used to define the cache region for the model beans.
Create Hibernate Utility class
For configuring hibernate on startup and managing session factory we create the HibernateUtil class like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package com.javabycode.hibernate; import org.hibernate.SessionFactory; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Configuration; import org.hibernate.service.ServiceRegistry; public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { Configuration configuration = new Configuration(); configuration.configure(); ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() .applySettings(configuration.getProperties()).build(); sessionFactory = new Configuration().configure().buildSessionFactory(serviceRegistry); } catch (Throwable ex) { System.err.println("Session Factory could not be created." + ex); throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return sessionFactory; } } |
Create test program for
Our Hibernate Second Level Cache Example will demonstrate two cases using Hibernate EHCache:
Case 1: Load data with same session object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package com.javabycode.hibernate; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.stat.Statistics; import com.javabycode.hibernate.model.Student; public class HibernateEHCacheExample1 { public static void main(String[] args) { System.out.println("java.io.tmpdir: " + System.getProperty("java.io.tmpdir")); SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); Statistics stats = sessionFactory.getStatistics(); stats.setStatisticsEnabled(true); //Enable statistics logs Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); printStats(stats); printLog("--------Step 1--------"); Student student = (Student) session.load(Student.class, 23L); printLog(student.getName()); printStats(stats); printLog("--------Step 2--------"); student = (Student) session.load(Student.class, 23L); printLog(student.getName()); printStats(stats); // clear first level cache, so that second level cache is used printLog("--------Step 3--------"); session.evict(student); student = (Student) session.load(Student.class, 23L); printLog(student.getName()); printStats(stats); // Release resources transaction.commit(); session.close(); } private static void printStats(Statistics stats) { System.out.println("Fetch Count=" + stats.getEntityFetchCount()); System.out.println("Second Level Hit Count=" + stats.getSecondLevelCacheHitCount()); System.out.println("Second Level Miss Count=" + stats.getSecondLevelCacheMissCount()); System.out.println("Second Level Put Count=" + stats.getSecondLevelCachePutCount()); } private static void printLog(String msg) { System.out.println(msg); } } |
Run above program and we get the output like below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Fetch Count=0 Second Level Hit Count=0 Second Level Miss Count=0 Second Level Put Count=0 --------Step 1-------- Hibernate: select student0_.id as id1_0_0_, student0_.CODE as CODE2_0_0_, student0_.ENTERING_DATE as ENTERING3_0_0_, student0_.NAME as NAME4_0_0_, student0_.NATIONALITY as NATIONAL5_0_0_ from STUDENT student0_ where student0_.id=? David Pham Fetch Count=1 Second Level Hit Count=0 Second Level Miss Count=1 Second Level Put Count=1 --------Step 2-------- David Pham Fetch Count=1 Second Level Hit Count=0 Second Level Miss Count=1 Second Level Put Count=1 --------Step 3-------- David Pham Fetch Count=1 Second Level Hit Count=1 Second Level Miss Count=1 Second Level Put Count=1 |
Step by step explanation of the output is as follows:
At the step 0: Our Second Level cache is empty, all the statistics are 0 as expected.
At the step 1: We loaded a student. Hibernate found this object in the Second Level cache firstly, if it doesn’t exist Hibernate looked up in database. It’s is reason why query log is fired. And this student object was stored in Hibernate Second Level Cache, the Second Level Put Count is set to 1.
At the step 2: There is no change with Statistics of Second Level cache. Because First Level cache is still enable and Hibernate looked up in it firstly. If object was found in the First Level cache Hibernate would skip over Second Level cache.
At the step 3: Firstly, we did evict student object in First Level cache. Then we loaded a student again. Hibernate first looked up in the First Level cache, but this object didn’t exist. Then Hibernate looked up in the Second Level cache so that Second Level Hit Count was set to 1.
Case 2: Load data with two other session objects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package com.javabycode.hibernate; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.stat.Statistics; import com.javabycode.hibernate.model.Student; public class HibernateEHCacheExample2 { public static void main(String[] args) { System.out.println("java.io.tmpdir: " + System.getProperty("java.io.tmpdir")); // Initialize Sessions SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); Statistics stats = sessionFactory.getStatistics(); stats.setStatisticsEnabled(true); //Enable statistics logs Session session1 = sessionFactory.openSession(); Session session2 = sessionFactory.openSession(); Transaction transaction1 = session1.beginTransaction(); Transaction transaction2 = session2.beginTransaction(); printStats(stats); printLog("--------Step 1--------"); Student student = (Student) session1.load(Student.class, 23L); printLog(student.getName()); printStats(stats); printLog("--------Step 2--------"); student = (Student) session2.load(Student.class, 23L); printLog(student.getName()); printStats(stats); // Release resources transaction1.commit(); transaction2.commit(); session1.close(); session2.close(); } private static void printStats(Statistics stats) { System.out.println("Fetch Count=" + stats.getEntityFetchCount()); System.out.println("Second Level Hit Count=" + stats.getSecondLevelCacheHitCount()); System.out.println("Second Level Miss Count=" + stats.getSecondLevelCacheMissCount()); System.out.println("Second Level Put Count=" + stats.getSecondLevelCachePutCount()); } private static void printLog(String msg) { System.out.println(msg); } } |
Run above main and we get the output like below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Fetch Count=0 Second Level Hit Count=0 Second Level Miss Count=0 Second Level Put Count=0 --------Step 1-------- Hibernate: select student0_.id as id1_0_0_, student0_.CODE as CODE2_0_0_, student0_.ENTERING_DATE as ENTERING3_0_0_, student0_.NAME as NAME4_0_0_, student0_.NATIONALITY as NATIONAL5_0_0_ from STUDENT student0_ where student0_.id=? David Pham Fetch Count=1 Second Level Hit Count=0 Second Level Miss Count=1 Second Level Put Count=1 --------Step 2-------- David Pham Fetch Count=1 Second Level Hit Count=1 Second Level Miss Count=1 Second Level Put Count=1 |
Step by step explanation of the output is as follows:
At the step 0: Our Second Level cache was empty, all the statistics was 0 as expected.
At the step 1: We loaded a student by using session1. Hibernate found this object in the Second Level cache firstly, if it doesn’t exist Hibernate looked up in database. It’s is reason why query log is fired. And this student object was stored in Hibernate Second Level Cache, the Second Level Put Count is set to 1.
At the step 2: We loaded a student by using session2. However, Hibernate found this student object in Second Level Cache and the Second Level Hit Count is set to 1. And that’s reason why query log was not fired. Notice that this student object was stored in session1 but not in session2
That’s all on the Hibernate Second Level Cache Example, I hope it will help you in configuring EHCache in your hibernate applications and getting better performance through hibernate second level cache. You can download the sample project from below link and use other stats data to learn more.
Download complete source code, please click link below
HibernateEHCacheExample.zip (417 downloads)