UPDATE: The below approaches have not aged well. See Hibernate with Infinispan
for a more up-to-date L2 cache solution.
Introduction
I needed to set up Hibernate cacheing and wanted to also use an L2 cache (Ehcache, in this case). Having not done this for quite a long time, I looked at a tutorial which led me to a simple set-up which worked well. However, that set-up used a relatively old version of Ehcache.
That initial set-up is shown below, followed by a more modern approach.
Set-up without JCache
I initially used the following dependencies:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.30.Final</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
<!-- extra for 2nd level cache -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.4.30.Final</version>
</dependency>
|
Here is the hibernate.cfg.xml
file - placed in src/main/resources
for this standalone Java SE application:
XML
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
|
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">org.h2.Driver</property>
<property name="connection.url">jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</property>
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">create</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.generate_statistics">false</property>
<!-- extra for 2nd level cache -->
<!-- https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#caching-config -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- mappings must be placed after properties -->
<mapping class="org.ajames.hibernateorm.Person"/>
</session-factory>
</hibernate-configuration>
|
The Person
class I used for basic testing of my set-up:
Java
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
|
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Cacheable;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
@Entity
//annotations for 2nd-level cache:
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Person implements Serializable {
@Id
@GeneratedValue(generator = "sequence-generator")
@GenericGenerator(
name = "sequence-generator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
@Parameter(name = "sequence_name", value = "user_sequence"),
@Parameter(name = "initial_value", value = "1"),
@Parameter(name = "increment_size", value = "1")
}
)
private int id;
@Column(name = "FULL_NAME")
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getters and setters not shown
@Override
public String toString() {
return String.join(", ", Integer.toString(id), name, Integer.toString(age));
}
}
|
Here is the main application class I used to create Hibernate and the L1 and L2 caches. It is not intended to be production-ready code - it is just a demo:
Java
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
|
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
//import org.hibernate.cfg.Configuration;
//import org.hibernate.service.ServiceRegistry;
public class App {
public static void main(String[] args) {
App app = new App();
// legacy approach:
//try ( SessionFactory sessionFactory = new Configuration().configure()
// .buildSessionFactory()) {
// app.persist(sessionFactory);
// app.loadAll(sessionFactory);
//}
// recommended approach:
try ( SessionFactory sessionFactory = app.createSessionFactory()) {
app.persist(sessionFactory);
app.loadAll(sessionFactory);
}
}
private SessionFactory createSessionFactory() throws Exception {
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure() // from hibernate.cfg.xml
.build();
SessionFactory sessionFactory = null;
try {
sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
} catch (Exception e) {
StandardServiceRegistryBuilder.destroy(registry);
}
return sessionFactory;
}
private void persist(SessionFactory sessionFactory) {
Person p1 = new Person("John", 35);
Person p2 = new Person("Tina", 30);
System.out.println();
System.out.println("-- persisting persons (before) --");
System.out.printf(" -> %s%n -> %s%n", p1, p2);
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(p1);
session.save(p2);
session.getTransaction().commit();
System.out.println();
System.out.println("-- persisting persons (after) --");
System.out.printf(" -> %s%n -> %s%n", p1, p2);
}
private void loadAll(SessionFactory sessionFactory) {
System.out.println();
System.out.println("-- loading persons --");
try ( Session session = sessionFactory.openSession()) {
List<Person> persons = session.createQuery("FROM Person p", Person.class).list();
persons.forEach((x) -> System.out.printf(" -> %s%n", x));
}
}
}
|
Deprecation warning
The above libraries generate a runtime warning message:
WARN: HHH020100: The Ehcache second-level cache provider for Hibernate is deprecated. See https://hibernate.atlassian.net/browse/HHH-12441
for details.
The link leads to a ticket which states:
Deprecate hibernate-ehcache module as it is using Ehcache 2 as its back-end, which is deprecated itself in favor of Ehcache 3. Ehcache 3 can be easily used instead by using the hibernate-jcache module and have Ehcache 3 (which is a JCache implementor) properly registered with JCache.
Specifically, in my case, the above set-up uses Ehcache 2.10.6, whereas the current release of Ehcache is 3.9.2.
Side note: The “official” current release of Ehcache is shown as version 3.8.1 on their official web site
, and is currently tagged as the “latest release” in GitHub
. This was released in September 2019. However, Maven
offers subsequent releases, including v3.9.2 from February 2021 - so that is what I will use here.
Using JCache
JCache is an API - so, it does not provide a caching implementation. But it does allow implementation providers to deliver products which conform to the API - as is the case with Ehcache 3.
This is exposed through the javax.cache
package (in cache-api-1.0.0.jar).
Dependencies
The new dependencies are:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.30.Final</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
<!-- extra for 2nd level cache -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jcache</artifactId>
<version>5.4.30.Final</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.2</version>
</dependency>
|
Configuration
The new hibernate.cfg.xml
:
XML
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
|
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">org.h2.Driver</property>
<property name="connection.url">jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</property>
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">create</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.generate_statistics">false</property>
<!-- extra for 2nd level cache -->
<!-- https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#caching-config -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">jcache</property>
<property name="hibernate.javax.cache.provider">org.ehcache.jsr107.EhcacheCachingProvider</property>
<!-- placed on the classpath (note: "classpath:ehcache.xml" does not work! -->
<property name="hibernate.javax.cache.uri">ehcache.xml</property>
<!--
<property name="hibernate.cache.use_query_cache">true</property>
-->
<!-- mappings must be placed after properties -->
<mapping class="org.ajames.hibernateorm.Person"/>
</session-factory>
</hibernate-configuration>
|
The ehcache.xml
- using a very basic cache template for finer-grained control over the Ehcache cache:
XML
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"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ehcache.org/v3"
xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
xsi:schemaLocation="
http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
<service>
<jsr107:defaults enable-management="true" enable-statistics="true"/>
</service>
<cache-template name="simple">
<expiry>
<ttl unit="minutes">2</ttl>
</expiry>
<heap unit="entries">100</heap>
</cache-template>
<cache alias="org.ajames.hibernateormnative.Person" uses-template="simple"/>
</config>
|
The Java code is unchanged - so, we are still using classes such as org.hibernate.SessionFactory
. In this sense, we have not taken full advantage of JCache, as we still have explicit dependencies on Ehcache classes. And we’re using ehcache.xml
…
The Java code could be re-written to use only the JCache API. Maybe another time. I am not sure about the ehcache.xml
config file. That would need a closer look.
More interestingly, I have specified this in ehcache.xml
:
enable-statistics="true"
To get statistics on the L2 cache, I will use the JMX API
- see the next post for details: Getting L2 Cache Statistics using Mbeans
.