Learn to setup and configure the L2 (second-level cache) in Hibernate 6 using Ehcache 3. This tutorial aims to provide an initial working setup and expects you to further study and customize the configuration according to your requirements.
1. Dependencies
This demo uses the hibernate’s built-in integration for JCache so we need to include the hibernate-jcache
module.
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>${javax.persistence.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jcache</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>${hibernate.ehcache.version}</version>
</dependency>
In addition, a JCache implementation needs to be added. We are using ehcache
so its related modules need to be included in the dependencies.
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>${ehcache.version}</version>
</dependency>
Since Java 11, JAXB has been removed from the JDK distribution so we need to import it explicitly. It is required to parse the ehcache.xml
file when we bootstrap the application and the configuration is parsed.
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>${jaxb.core.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb.api.version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.api.version}</version>
</dependency>
Apart from the above-listed dependencies, we obviously need to add other required modules such as hibernate, persistence, datasource, logging and unit testing.
2. Enable L2 Cache Configuration
To enable the second-level cache support, we need to enable it in hibernate.cf.xml
file or supplying properties in Java-based configuration for SessionFactory
.
<property name="hibernate.cache.region.factory_class">jcache</property>
<property name="hibernate.javax.cache.provider">org.ehcache.jsr107.EhcacheCachingProvider</property>
<property name="hibernate.javax.cache.uri">ehcache.xml</property>
<property name="hibernate.cache.use_second_level_cache">true</property>
The hibernate.cache.region.factory_class
is used to declare the provider to use. Here we are using EhcacheCachingProvider
that configures the ehcache for us.
We may enable the statistics to verify that cache is working as expected.
<property name="hibernate.generate_statistics">true</property>
Finally, define the entities specific cache settings in ehcache.xml
.
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xsi:schemaLocation="
https://www.ehcache.org/v3/ https://www.ehcache.org/schema/ehcache-core-3.0.xsd">
<cache alias="employee">
<key-type>java.lang.Long</key-type>
<value-type>com.howtodoinjava.basics.entity.EmployeeEntity</value-type>
<heap unit="entries">10000</heap>
</cache>
</config>
3. Make Entities @Cacheable
The @Cacheable
annotation is used to specify whether an entity should be stored in the second-level cache. And the @Cache
annotation is used to specify the CacheConcurrencyStrategy
of a root entity or a collection.
import jakarta.persistence.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Cache;
import java.io.Serial;
import java.io.Serializable;
@Entity
@Table(name = "Employee", uniqueConstraints = {
@UniqueConstraint(columnNames = "ID"),
@UniqueConstraint(columnNames = "EMAIL") })
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class EmployeeEntity implements Serializable {
@Serial
private static final long serialVersionUID = -1798070786993154676L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID", unique = true, nullable = false)
private Integer employeeId;
@Column(name = "EMAIL", unique = true, nullable = false, length = 100)
private String email;
@Column(name = "FIRST_NAME", nullable = false, length = 100)
private String firstName;
@Column(name = "LAST_NAME", nullable = false, length = 100)
private String lastName;
//Getters and setters are hidden for brevity
}
4. Demo
Now it’s time to test the second-level cache configuration by running a few tests. We are using JUnit 5 for executing tests that persist the data in H2 database.
To test the configuration, we have two options:
- Verify the cache
HIT
statistics in the console - Use the
sessionFactory.getStatistics().getSecondLevelCacheHitCount()
method and ensure that it matches as expected.
In the given tests, we are creating an EmployeeEntity instance and saving it to the database. Then we fetch it from the database multiple times.
import com.howtodoinjava.basics.entity.EmployeeEntity;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Environment;
import org.junit.jupiter.api.*;
public class TestSecondLevelCache {
private static SessionFactory sessionFactory = null;
private Session session = null;
@BeforeAll
static void setup(){
try {
StandardServiceRegistry standardRegistry
= new StandardServiceRegistryBuilder()
.configure("hibernate-test.cfg.xml")
.build();
Metadata metadata = new MetadataSources(standardRegistry)
.addAnnotatedClass(EmployeeEntity.class)
.getMetadataBuilder()
.build();
sessionFactory = metadata
.getSessionFactoryBuilder().build();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
@BeforeEach
void setupThis(){
session = sessionFactory.openSession();
session.beginTransaction();
}
@AfterEach
void tearThis(){
session.getTransaction().commit();
}
@AfterAll
static void tear(){
sessionFactory.close();
}
@Test
void createSessionFactoryWithXML() {
EmployeeEntity emp = new EmployeeEntity();
emp.setEmail("demo-user@mail.com");
emp.setFirstName("demo");
emp.setLastName("user");
Assertions.assertNull(emp.getEmployeeId());
session.persist(emp);
Assertions.assertNotNull(emp.getEmployeeId());
EmployeeEntity cachedEmployee = session.get(EmployeeEntity.class,
emp.getEmployeeId());
session.flush();
session.close();
//New Session
session = sessionFactory.openSession();
session.beginTransaction();
cachedEmployee = session.get(EmployeeEntity.class,
emp.getEmployeeId());
Assertions.assertEquals(0,
session.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());
session.flush();
session.close();
//New Session
session = sessionFactory.openSession();
session.beginTransaction();
cachedEmployee = session.get(EmployeeEntity.class,
emp.getEmployeeId());
Assertions.assertEquals(1,
session.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());
session.flush();
session.close();
//New Session
session = sessionFactory.openSession();
session.beginTransaction();
cachedEmployee = session.get(EmployeeEntity.class,
emp.getEmployeeId());
Assertions.assertEquals(2,
session.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());
}
}
The first time, the HIT count will be 0
and the PUT count will be 1
.
27391000 nanoseconds spent performing 1 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
1195400 nanoseconds spent performing 1 L2C misses;
The second time, HIT count will be 1
and others will be 0
.
0 nanoseconds spent performing 0 L2C puts;
1012300 nanoseconds spent performing 1 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
Similarly, the HIT count will increment by 1
everytime we fetch the same entity over and over again. It proves that the L2 cache has been configured and working as expected.
5. Conclusion
In this hibernate tutorial, we learned to configure the Ehcache 3 with Hibernate 6. We used the hibernate’s internal JCache implementation and plugged in the Ehcache as cache provider.
Finally, we verified that caching is working as expected in a unit test.
Happy Learning !!