Caching is one of the main advantage hibernate has over normal JDBC. This article briefs the caching support in hibernate. It explains Hibernate Second Level Cache in detail along with steps to configure it using EH Cache provider.
Why Cache?
First, let’s know why we need to cache any data. For optimum performance of application its always recommended minimizing the database calls. The reason is, most of the time database call is heavy operation and hence reduces the response time of an application. In an application, there are chances that some set of data won’t change frequently but requires at many places. In those cases, its ideal to cache such data and minimize the database calls to fetch it. Any read-only data with high usage are most eligible for caching. However, we can also cache data involve in read and write operations.
For example in an Employee management system suppose adding and modifying of employee details are frequent operations. Each employee belongs to a department. On add/modify user interface, departments should be shown in a drop-down list for users to choose from. Departments of an organization never go under frequent changes. In this case, it’s better to cache the list of departments so no need to fetch them each time.
Note: While caching looks helpful to keep the objects loaded in memory and skip the database calls, we should take care that we are not caching too much of data. Caching lots of objects will degrade application performance as it will be overhead to memory.
Spring 4 + Hibernate 5 Example
Hibernate Cache Support
Hibernate provides three types of caching:
Session Cache
Also known as a first level cache which is associated with current Session. It is provided by default and you cannot disable it. But you can remove an object or clear the entire cache. The main purpose of this cache is to limit the number of SQL query executions within a transaction. All modifications will be committed to the database at the end of a transaction.
Second Level Cache
This cache is associated with Session Factory and available to all sessions. You need to configure it explicitly if you want to use it. When enabled, a database query will be fired only if hibernate don’t get the required objects in the second level cache. There are multiple providers of this cache and you need to set the concurrency strategy before using it, more on this in next section.
Query Cache
As name suggest, query cache will cache the result set of a query. More precisely keys of the objects returned. The result set is associated with parameters passed to the query. You need to configure it explicitly and should be used in conjunction second level cache.
Hibernate Second Level Cache
As this article is for hibernate second level cache, let’s get some more details about it. As mentioned in the earlier section we need to decide on provider and concurrency strategy. First start with Strategy:
Cache Concurrency Strategy
Read-only
Suitable to cache read-only data which never gets updated. Example, reference or configuration data.
Read-write
Suitable when data involves in read-write operations. Use this when it’s critical to avoid stale data situation in concurrent transactions.
Nonstrict-read-write
Similar to read-write but if there are very rare chances of update operation and stale data situation is not critical use this strategy.
Transactional
Works similar to Read-write but provides serializable transaction isolation level.
Cache Provider
After you decide on cache provider, you need to specify the cache region factory class to use. Below are details of cache providers and their features:
JCache
JCache is a common API for caching in Java. JCacheRegionFactory provided in hibernate-jcache module needs to be used here. There are number of implementations(listed here) available for JCache specifications. You need to add libraries of those implementations when using JCacheRegionFactory.
EhCache
EhCache is most popular cache provider for hibernate. The hibernate-ehcache has two region factories: EhCacheRegionFactory and SingletonEhCacheRegionFactory. If you want to share the same EhCache configuration between multiple sessionFactory instances in same JVM then go for SingletonEhCacheRegionFactory. However, EhCache documentation recommends using non-singleton.
EhCache supports read-only, read-write and nonstrict-read-write concurrency strategies. Our example will use EhCache provider to demonstrate the second level caching.
Infinispan
Infinispan comes with two configurations: single node local environment and multi-node cluster environment. The default configuration is for multi-node cluster environment. Since version 5.0, it supports all concurrency strategies. There are many other configurations of Infinispan, which are not in the scope of this article. Maybe another article specific to Infinispan will be provided for that.
Steps to configure Hibernate Second Level Cache
At the broad level following are the steps to configure Hibernate Second Level Cache:
- Add required libraries for cache provider
- Enable second level cache for hibernate
- Specify cache region factory class
- Add configuration file of cache provider
- Define cache concurrency strategy for entities to cache
Below are some details about these steps. We are using ehCache in our example. The complete code is available at the end of this article.
Add required libraries for cache provider
Separate libraries are available for each cache provider. For ehCache, hibernate-ehcache should be added. Make sure that version number matches with your hibernate-core library. You may need to add some other libraries required for cache provider. Maven will include an appropriate version of ehcache.jar once you include hibernate-ehcache library.
<dependencies> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-c3p0</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-ehcache</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.7</version> </dependency> <!-- Database --> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsqldb.version}</version> </dependency> </dependencies>
Enable second level cache for hibernate
This just requires to set hibernate.cache.use_second_level_cache property in hibernate config/properties file to true.
hibernate.cache.use_second_level_cache=true
Specify cache region factory class
Just set the hibernate.cache.region.factory_class property to region factory class of your cache provider. Like for ehcache, it should be org.hibernate.cache.ehcache.EhCacheRegionFactory and for Infinspan it should be org.hibernate.cache.infinispan.InfinispanRegionFactory.
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
Add configuration file of cache provider
You can configure cache provider’s behavior in the configuration file. Like setting default behavior all cache regions. Configuring specific cache region and define timeToIdleSeconds and timeToLiveSeconds properties for each region. Define local path on disk to store cache using DiskStore option.
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <diskStore path="java.io.tmpdir/ehcache" /> <defaultCache maxEntriesLocalHeap="100" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="200"> <persistence strategy="localTempSwap" /> </defaultCache> <cache name="department" maxEntriesLocalHeap="200" eternal="false" timeToIdleSeconds="60" timeToLiveSeconds="200"> <persistence strategy="localTempSwap" /> </cache> </ehcache>
Define cache concurrency strategy for entities to cache
You need to define the cache concurrency strategy in hibernate Entity class using the @cache annotation like this:
@Entity @Table(name = "department") @Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="department") public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name") private String name; }
The above example uses Read-only strategy and “department” cache region configured in ehcache.xml
EhCache demo application
Our demo application uses ehCache with above configuration. Departments data will be loaded at the start of the application through import.sql. The getData() method retrieves a department with id = 1 and called twice. On the first call you will see the SQL query fired by hibernate to retrieve the data. When the same department was retrieved second time, hibernate will not fire any database query because it is already present in the cache.
public class TestCache { private static void getData() { Session session = null; Transaction tx = null; try { session = HibernateUtils.getSessionFactory().openSession(); tx = session.beginTransaction(); System.out.println("\nRetrieving data...."); Department department = session.load(Department.class, new Long(1)); System.out.println("Department retrieved: " + department); } catch (Exception e) { e.printStackTrace(); } finally { tx.commit(); session.close(); } } public static void main(String[] args) { getData(); System.out.println("\nCalling getData() second time...."); getData(); HibernateUtils.getSessionFactory().close(); } }
The console log :
Hibernate: drop table department if exists Hibernate: create table department (id bigint generated by default as identity (start with 1), name varchar(255), primary key (id)) Hibernate: INSERT INTO department (name) VALUES ('HR') Hibernate: INSERT INTO department (name) VALUES ('ADMIN') Retrieving data.... Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from department department0_ where department0_.id=? Department retrieved: Id: 1 name: HR Calling getData() second time.... Retrieving data.... Department retrieved: Id: 1 name: HR Hibernate: drop table department if exists
Source Code
The complete source code is available at https://github.com/bytestree/hibernate-second-cache.git