My old approach to using Hibernate with a L2 cache has not aged well.
Here is a newer version. It uses Infinispan instead of Ehcache - because that integration is provided for us, and the Hibernate-Ehcache integration does not seem to be maintained. This also does not use JCache (although I suppose it could be enhanced for that).
In this very simple demo, I use an in-memory H2 database.
There is no custom Infinispan config file, here.
The Maven dependencies:
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
| <dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.1.7.Final</version>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-hibernate-cache-v60</artifactId>
<version>14.0.7.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
</dependency>
</dependencies>
|
My hibernate.cfg.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
33
34
35
36
37
38
39
40
41
| <?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.h2.Driver</property>
<property name="connection.url">jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Infinispan L2 cache -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">infinispan</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.generate_statistics">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<!-- The annotated entity class -->
<mapping class="com.northcoder.hibernate6.Event"/>
</session-factory>
</hibernate-configuration>
|
My entity class (based on the tutorial bundled with the Hibernate 6 release zip - so, yes, it uses Date
instead of java.time
).
See here for different cache concurrency strategies.
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
| package com.northcoder.hibernate6;
import jakarta.persistence.Cacheable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import java.util.Date;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "EVENTS")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Event {
private Long id;
private String title;
private Date date;
public Event() {
// this form used by Hibernate
}
public Event(String title, Date date) {
// for application use, to create new events
this.title = title;
this.date = date;
}
@Id
@GeneratedValue(generator = "increment")
@GenericGenerator(name = "increment", strategy = "increment")
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "EVENT_DATE")
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
|
The demo class:
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
| package com.northcoder.hibernate6;
import java.util.Date;
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.stat.CacheRegionStatistics;
import org.hibernate.stat.Statistics;
public class App {
private SessionFactory sessionFactory;
public static void main(String[] args) {
App app = new App();
app.setup();
app.createEvents();
app.fetchEvent();
app.shutDown();
}
private void setup() {
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure() // configures settings from hibernate.cfg.xml
.build();
try {
sessionFactory = new MetadataSources(registry)
.buildMetadata()
.buildSessionFactory();
} catch (Exception e) {
StandardServiceRegistryBuilder.destroy(registry);
}
}
private void createEvents() {
// create a couple of events...
Session session = sessionFactory.openSession();
session.beginTransaction();
session.persist(new Event("Our very first event!", new Date()));
session.persist(new Event("A follow up event", new Date()));
session.getTransaction().commit();
session.close();
session = sessionFactory.openSession();
session.beginTransaction();
List<Event> result = session.createQuery("from Event", Event.class).list();
System.out.println();
for (Event event : result) {
System.out.println("Event (" + event.getDate() + ") : " + event.getTitle());
}
System.out.println();
session.getTransaction().commit();
Event event = session.get(Event.class, 1L);
System.out.println("Event (" + event.getDate() + ") : " + event.getTitle());
System.out.println();
session.close();
}
private void fetchEvent() {
try (Session session = sessionFactory.openSession()) {
System.out.println();
Event event = session.get(Event.class, 1L);
System.out.println("Event from fetch (" + event.getDate() + ") : " + event.getTitle());
System.out.println();
// example of getting statistics programmatically
Statistics statistics = session.getSessionFactory().getStatistics();
CacheRegionStatistics secondLevelCacheStatistics
= statistics.getDomainDataRegionStatistics("com.northcoder.hibernate6.Event");
long hitCount = secondLevelCacheStatistics.getHitCount();
long missCount = secondLevelCacheStatistics.getMissCount();
double hitRatio = (double) hitCount / (hitCount + missCount);
System.out.println("Fetch hit count: " + hitCount);
System.out.println("Fetch miss count: " + missCount);
System.out.println("Fetch hit ratio: " + hitRatio);
System.out.println();
}
}
private void shutDown() {
if (sessionFactory != null) {
sessionFactory.close();
}
}
}
|
The above code includes an example of reading cache statistics programmatically - so, for example:
1
2
3
4
5
| Event from fetch (2023-03-27 15:18:23.866) : Our very first event!
Fetch hit count: 1
Fetch miss count: 0
Fetch hit ratio: 1.0
|
But it also generates the following information, based on the Hibernate configuration:
1
2
3
4
5
6
7
8
9
10
11
12
| INFO: Session Metrics {
0 nanoseconds spent acquiring 0 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
0 nanoseconds spent preparing 0 JDBC statements;
0 nanoseconds spent executing 0 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
336800 nanoseconds spent performing 1 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
|
These are logged whenever a session closes - which may result in a lot of unwanted logging info. You can suppress these session metrics by adding the following to hibernate.cfg.xml
1
| <property name="hibernate.session.events.log">false</property>
|