Hibernate Second-Level Cache
Overview
One of the advantages of database abstraction layers such as ORM (object-relational mapping) frameworks is their ability to transparently cache data retrieved from the underlying store. This helps eliminate database-access costs for frequently accessed data.
Performance gains can be significant if read/write ratios of cached content are high, especially for entities that consist of large object graphs.
2. What Is a Second-Level Cache?
Like most other fully-equipped ORM frameworks, Hibernate has the concept of the first-level cache. It is a session-scoped cache which ensures that each entity instance is loaded only once in the persistent context.
Once the session is closed, the first-level cache is terminated as well. This is desirable, as it allows for concurrent sessions to work with entity instances in isolation from each other.
On the other hand, the second-level cache is SessionFactory-scoped, meaning it is shared by all sessions created with the same session factory. When an entity instance is looked up by its id (either by application logic or by Hibernate internally, e.g. when it loads associations to that entity from other entities), and if second-level caching is enabled for that entity, the following happens:
- If an instance is already present in the first-level cache, it is returned from there
- If an instance is not found in the first-level cache, and the corresponding instance state is cached in the second-level cache, then the data is fetched from there and an instance is assembled and returned
- Otherwise, the necessary data are loaded from the database and an instance is assembled and returned
Once the instance is stored in the persistence context (first-level cache), it is returned from there in all subsequent calls within the same session until the session is closed or the instance is manually evicted from the persistence context. Also, the loaded instance state is stored in the L2 cache if it was not there already.
Hibernate second-level cache uses a common cache for all the session objects of a session factory. It is useful if you have multiple session objects from a session factory.
SessionFactory holds the second level cache data. It is global for all the session objects and not enabled by default.
Different vendors have provided the implementation of Second Level Cache.
- EH Cache
- OS Cache
- Swarm Cache
- JBoss Cache
Each implementation provides different cache usage functionality. There are four ways to use the second-level cache.
- read-only: caching will work for reading the only operation.
- nonstrict-read-write: caching will work to read and write but one at a time.
- read-write: caching will work to read and write, can be used simultaneously.
- transactional: caching will work for transactions.
Region Factory
Hibernate second-level caching is designed to be unaware of the actual cache provider used. Hibernate only needs to be provided with an implementation of the org.hibernate.cache.spi.RegionFactory interface which encapsulates all details specific to actual cache providers. It acts as a bridge between Hibernate and cache providers.
In this article, we use Ehcache as a cache provider, which is a mature and widely used cache. You can pick any other provider of course, as long as there is an implementation of a RegionFactory for it.
We add the Ehcache region factory implementation to the classpath with the following Maven dependency:
1 2 3 4 5 |
|
Make sure that the hibernate-ehcache version is equal to the Hibernate version which you use in your project, e.g. if you use hibernate-ehcache 5.2.2. Final like in this example, then the version of Hibernate should also be 5.2.2. Final.
The hibernate-ehcache artifact has a dependency on the Ehcache implementation itself, which is thus transitively included in the classpath as well.
4. Enabling Second-Level Caching
With the following two properties we tell Hibernate that L2 caching is enabled and we give it the name of the region factory class:
1 2 |
|
For example, in persistence.xml it would look like:
1 2 3 4 5 6 7 |
|
To disable second-level caching (for debugging purposes for example), just set hibernate.cache.use_second_level_cache property to false.
Making an Entity Cacheable
To make an entity eligible for second-level caching, we annotate it with Hibernate specific @org.hibernate.annotations.Cache annotation and specify a cache concurrency strategy.
Some developers consider that it is a good convention to add the standard @javax.persistence.Cacheable annotation as well (although not required by Hibernate), so an entity class implementation might look like this:
- package com.prejava;
- import javax.persistence.*;
- import org.hibernate.annotations.Cache;
- import org.hibernate.annotations.CacheConcurrencyStrategy;
- @Entity
- @Table(name="EMPLOYEE")
- @Cacheable
- @Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
- public class Employee {
- @Id
- private int id;
- private String name;
- private float salary;
- public Employee() {}
- public Employee(String name, float salary) {
- super();
- this.name = name;
- this.salary = salary;
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public float getSalary() {
- return salary;
- }
- public void setSalary(float salary) {
- this.salary = salary;
- }
- }
For each entity class, Hibernate will use a separate cache region to store the state of instances for that class. The region name is the fully qualified class name.
Cache Concurrency Strategy
Based on use cases, we are free to pick one of the following cache concurrency strategies:
- READ_ONLY: Used only for entities that never change (exception is thrown if an attempt to update such an entity is made). It is very simple and performant. Very suitable for some static reference data that don't change
- NONSTRICT_READ_WRITE: Cache is updated after a transaction that changed the affected data has been committed. Thus, strong consistency is not guaranteed and there is a small time window in which stale data may be obtained from the cache. This kind of strategy is suitable for use cases that can tolerate eventual consistency
- READ_WRITE: This strategy guarantees strong consistency which it achieves by using ‘soft' locks: When a cached entity is updated, a soft lock is stored in the cache for that entity as well, which is released after the transaction is committed. All concurrent transactions that access soft-locked entries will fetch the corresponding data directly from the database
- TRANSACTIONAL: Cache changes are done in distributed XA transactions. A change in a cached entity is either committed or rolled back in both database and cache in the same XA transaction
post a comment