Willkommen zum Hibernate Second Level Cache Example Tutorial. Heute werden wir uns Hibernate EHCache ansehen, das der beliebteste Hibernate Second Level Cache-Anbieter ist.
Hibernate Second Level Cache
Einer der Hauptvorteile der Verwendung von Hibernate in einer großen Anwendung ist die Unterstützung für den Cache, wodurch Datenbankabfragen reduziert und die Leistung verbessert werden. In einem früheren Beispiel haben wir uns den Hibernate First Level Cache angesehen, und heute werden wir uns den Hibernate Second Level Cache mit der Implementierung von Hibernate EHCache ansehen. Zu den Hibernate Second Level Cache-Anbietern gehören EHCache und Infinispan, aber EHCache ist beliebter und wir werden es für unser Beispielprojekt verwenden. Bevor wir jedoch zu unserem Projekt übergehen, sollten wir verschiedene Strategien kennen, um ein Objekt zu zwischenspeichern.
- Nur lesen: Diese Zwischenspeicherungsstrategie sollte für persistente Objekte verwendet werden, die immer gelesen, aber nie aktualisiert werden. Sie eignet sich gut zum Lesen und Zwischenspeichern von Anwendungskonfigurationen und anderen statischen Daten, die nie aktualisiert werden. Dies ist die einfachste Strategie mit der besten Leistung, da keine Überlastung besteht, um zu überprüfen, ob das Objekt in der Datenbank aktualisiert wurde oder nicht.
- Lesen Schreiben: Es ist gut für persistente Objekte, die von der Hibernate-Anwendung aktualisiert werden können. Wenn die Daten jedoch entweder über das Backend oder andere Anwendungen aktualisiert werden, wird Hibernate nichts darüber wissen, und die Daten könnten veraltet sein. Stellen Sie daher sicher, dass Sie beim Verwenden dieser Strategie die Hibernate-API zum Aktualisieren der Daten verwenden.
- Nicht eingeschränktes Lesen Schreiben: Wenn die Anwendung nur gelegentlich Daten aktualisieren muss und strenge Transaktionsisolierung nicht erforderlich ist, könnte ein nicht strenges Lesen-Schreiben-Cache angemessen sein.
- Transaktional: Die transaktionale Cache-Strategie bietet Unterstützung für vollständig transaktionale Cache-Provider wie JBoss TreeCache. Ein solcher Cache kann nur in einer JTA-Umgebung verwendet werden, und Sie müssen hibernate.transaction.manager_lookup_class angeben.
Hibernate EHCache
Da EHCache alle oben genannten Cache-Strategien unterstützt, ist es die beste Wahl, wenn Sie nach einem Cache der zweiten Ebene in Hibernate suchen. Ich werde nicht näher auf EHCache eingehen, mein Hauptaugenmerk wird darauf liegen, ihn für die Hibernate-Anwendung zum Laufen zu bringen. Erstellen Sie ein Maven-Projekt in Eclipse oder Ihrer Lieblings-IDE, die endgültige Implementierung wird wie im folgenden Bild aussehen. Schauen wir uns jedes Komponente der Anwendung nacheinander an.
Hibernate EHCache Maven Abhängigkeiten
Für den Hibernate Second-Level-Cache müssen wir ehcache-core und hibernate-ehcache Abhängigkeiten in unserer Anwendung hinzufügen. EHCache verwendet slf4j für das Logging, daher habe ich auch slf4j-simple zum Loggen hinzugefügt. Ich verwende die neuesten Versionen all dieser APIs, es besteht eine geringe Chance, dass die hibernate-ehcache APIs nicht mit der ehcache-core API kompatibel sind, in diesem Fall müssen Sie die pom.xml von hibernate-ehcache überprüfen, um die richtige Version zu finden. Unsere endgültige pom.xml sieht wie unten aus.
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.hibernate</groupId>
<artifactId>HibernateEHCacheExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>Hibernate Secondary Level Cache Example using EHCache implementation</description>
<dependencies>
<!-- Hibernate Core API -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.5.Final</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
</dependency>
<!-- EHCache Core APIs -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.9</version>
</dependency>
<!-- Hibernate EHCache API -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>4.3.5.Final</version>
</dependency>
<!-- EHCache uses slf4j for logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
</project>
Hibernate Second Level Cache – Hibernate EHCache Konfiguration
Der Hibernate Second-Level-Cache ist standardmäßig deaktiviert, daher müssen wir ihn aktivieren und einige Konfigurationen hinzufügen, um ihn zum Laufen zu bringen. Unsere hibernate.cfg.xml-Datei sieht wie folgt aus.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration SYSTEM "classpath://org/hibernate/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.password">pankaj123</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
<property name="hibernate.connection.username">pankaj</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- For singleton factory -->
<!-- <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</property>
-->
<!-- enable second level cache and query cache -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.use_query_cache">true</property>
<property name="net.sf.ehcache.configurationResourceName">/myehcache.xml</property>
<mapping class="com.journaldev.hibernate.model.Employee" />
<mapping class="com.journaldev.hibernate.model.Address" />
</session-factory>
</hibernate-configuration>
Einige wichtige Punkte über die Konfigurationen des Hibernate Second-Level-Caches sind:
- hibernate.cache.region.factory_class wird verwendet, um die Factory-Klasse für die Second-Level-Cache zu definieren. Ich verwende
org.hibernate.cache.ehcache.EhCacheRegionFactory
dafür. Wenn Sie möchten, dass die Factory-Klasse ein Singleton ist, sollten Sie die Klasseorg.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
verwenden. Wenn Sie Hibernate 3 verwenden, lauten die entsprechenden Klassennet.sf.ehcache.hibernate.EhCacheRegionFactory
undnet.sf.ehcache.hibernate.SingletonEhCacheRegionFactory
. - hibernate.cache.use_second_level_cache wird verwendet, um den Second-Level-Cache zu aktivieren.
- hibernate.cache.use_query_cache wird verwendet, um den Abfrage-Cache zu aktivieren. Ohne ihn werden die Ergebnisse von HQL-Abfragen nicht zwischengespeichert.
- net.sf.ehcache.configurationResourceName wird verwendet, um den Standort der EHCache-Konfigurationsdatei festzulegen. Es handelt sich um einen optionalen Parameter, und wenn er nicht vorhanden ist, versucht EHCache, die Datei ehcache.xml im Klassenpfad der Anwendung zu finden.
Hibernate EHCache-Konfigurationsdatei
Unsere EHCache-Konfigurationsdatei myehcache.xml sieht folgendermaßen aus.
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
monitoring="autodetect" dynamicConfig="true">
<diskStore path="java.io.tmpdir/ehcache" />
<defaultCache maxEntriesLocalHeap="10000" eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30"
maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU" statistics="true">
<persistence strategy="localTempSwap" />
</defaultCache>
<cache name="employee" maxEntriesLocalHeap="10000" eternal="false"
timeToIdleSeconds="5" timeToLiveSeconds="10">
<persistence strategy="localTempSwap" />
</cache>
<cache name="org.hibernate.cache.internal.StandardQueryCache"
maxEntriesLocalHeap="5" eternal="false" timeToLiveSeconds="120">
<persistence strategy="localTempSwap" />
</cache>
<cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
maxEntriesLocalHeap="5000" eternal="true">
<persistence strategy="localTempSwap" />
</cache>
</ehcache>
Hibernate EHCache bietet viele Optionen. Ich gehe nicht detailliert darauf ein, aber einige wichtige Konfigurationen oben sind:
- diskStore: EHCache speichert Daten im Speicher, aber wenn es überläuft, beginnt es, Daten in das Dateisystem zu schreiben. Wir verwenden diese Eigenschaft, um den Speicherort zu definieren, an dem EHCache die überlaufenden Daten schreiben wird.
- defaultCache: Es handelt sich um eine obligatorische Konfiguration, die verwendet wird, wenn ein Objekt zwischengespeichert werden muss und keine Zwischenspeicherregionen dafür definiert sind.
- cache name=“employee”: Wir verwenden das Cache-Element, um die Region und ihre Konfigurationen zu definieren. Wir können mehrere Regionen und ihre Eigenschaften definieren. Beim Definieren von Model-Beans-Cache-Eigenschaften können wir auch Regionen mit Caching-Strategien definieren. Die Cache-Eigenschaften sind leicht verständlich und klar benannt.
- Cache-Regionen
org.hibernate.cache.internal.StandardQueryCache
undorg.hibernate.cache.spi.UpdateTimestampsCache
werden definiert, weil EHCache darauf Warnungen gegeben hat.
Hibernate Second Level Cache – Model Bean Caching Strategy
Wir verwenden die Annotation org.hibernate.annotations.Cache
, um die Caching-Konfiguration bereitzustellen. org.hibernate.annotations.CacheConcurrencyStrategy
wird verwendet, um die Caching-Strategie zu definieren, und wir können auch die Cache-Region definieren, die für die Model-Beans verwendet werden soll.
package com.journaldev.hibernate.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
@Entity
@Table(name = "ADDRESS")
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="employee")
public class Address {
@Id
@Column(name = "emp_id", unique = true, nullable = false)
@GeneratedValue(generator = "gen")
@GenericGenerator(name = "gen", strategy = "foreign",
parameters = { @Parameter(name = "property", value = "employee") })
private long id;
@Column(name = "address_line1")
private String addressLine1;
@Column(name = "zipcode")
private String zipcode;
@Column(name = "city")
private String city;
@OneToOne
@PrimaryKeyJoinColumn
private Employee employee;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
}
package com.journaldev.hibernate.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Cascade;
@Entity
@Table(name = "EMPLOYEE")
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "emp_id")
private long id;
@Column(name = "emp_name")
private String name;
@Column(name = "emp_salary")
private double salary;
@OneToOne(mappedBy = "employee")
@Cascade(value = org.hibernate.annotations.CascadeType.ALL)
private Address address;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
Beachten Sie, dass ich dieselbe Datenbankkonfiguration verwende wie im HQL-Beispiel. Möglicherweise möchten Sie das überprüfen, um die Datenbanktabellen zu erstellen und Beispieldaten zu laden.
Hibernate SessionFactory Utility-Klasse
Wir haben eine einfache Hilfsklasse, um Hibernate zu konfigurieren und die Singleton-Instanz der SessionFactory
zu erhalten.
package com.journaldev.hibernate.util;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
public class HibernateUtil {
private static SessionFactory sessionFactory;
private static SessionFactory buildSessionFactory() {
try {
// Erstellen Sie die SessionFactory aus hibernate.cfg.xml
Configuration configuration = new Configuration();
configuration.configure("hibernate.cfg.xml");
System.out.println("Hibernate Configuration loaded");
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
System.out.println("Hibernate serviceRegistry created");
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
return sessionFactory;
}
catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
ex.printStackTrace();
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
if(sessionFactory == null) sessionFactory = buildSessionFactory();
return sessionFactory;
}
}
Unser Hibernate Second-Level-Cache-Projekt mit Hibernate EHCache ist bereit, lassen Sie uns ein einfaches Programm schreiben, um es zu testen.
Hibernate EHCache Testprogramm
package com.journaldev.hibernate.main;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.stat.Statistics;
import com.journaldev.hibernate.model.Employee;
import com.journaldev.hibernate.util.HibernateUtil;
public class HibernateEHCacheMain {
public static void main(String[] args) {
System.out.println("Temp Dir:"+System.getProperty("java.io.tmpdir"));
// Initialisieren Sie Sitzungen
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
Statistics stats = sessionFactory.getStatistics();
System.out.println("Stats enabled="+stats.isStatisticsEnabled());
stats.setStatisticsEnabled(true);
System.out.println("Stats enabled="+stats.isStatisticsEnabled());
Session session = sessionFactory.openSession();
Session otherSession = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Transaction otherTransaction = otherSession.beginTransaction();
printStats(stats, 0);
Employee emp = (Employee) session.load(Employee.class, 1L);
printData(emp, stats, 1);
emp = (Employee) session.load(Employee.class, 1L);
printData(emp, stats, 2);
// Löschen Sie den First-Level-Cache, damit der Second-Level-Cache verwendet wird
session.evict(emp);
emp = (Employee) session.load(Employee.class, 1L);
printData(emp, stats, 3);
emp = (Employee) session.load(Employee.class, 3L);
printData(emp, stats, 4);
emp = (Employee) otherSession.load(Employee.class, 1L);
printData(emp, stats, 5);
// Ressourcen freigeben
transaction.commit();
otherTransaction.commit();
sessionFactory.close();
}
private static void printStats(Statistics stats, int i) {
System.out.println("***** " + i + " *****");
System.out.println("Fetch Count="
+ stats.getEntityFetchCount());
System.out.println("Second Level Hit Count="
+ stats.getSecondLevelCacheHitCount());
System.out
.println("Second Level Miss Count="
+ stats
.getSecondLevelCacheMissCount());
System.out.println("Second Level Put Count="
+ stats.getSecondLevelCachePutCount());
}
private static void printData(Employee emp, Statistics stats, int count) {
System.out.println(count+":: Name="+emp.getName()+", Zipcode="+emp.getAddress().getZipcode());
printStats(stats, count);
}
}
org.hibernate.stat.Statistics
bietet die Statistiken der Hibernate SessionFactory, wir verwenden sie, um die Abrufanzahl und die Treffer-, Fehl- und Speicheranzahl des Caches der zweiten Ebene von Hibernate zu drucken. Statistiken sind standardmäßig deaktiviert, um die Leistung zu verbessern, deshalb aktiviere ich sie zu Beginn des Programms. Wenn wir das obige Programm ausführen, erhalten wir eine Menge Ausgaben, die von den Hibernate- und EHCache-APIs generiert wurden, aber wir sind an den Daten interessiert, die wir drucken. Ein Beispiel für eine Ausführung gibt die folgende Ausgabe aus.
Temp Dir:/var/folders/h4/q73jjy0902g51wkw0w69c0600000gn/T/
Hibernate Configuration loaded
Hibernate serviceRegistry created
Stats enabled=false
Stats enabled=true
***** 0 *****
Fetch Count=0
Second Level Hit Count=0
Second Level Miss Count=0
Second Level Put Count=0
Hibernate: select employee0_.emp_id as emp_id1_1_0_, employee0_.emp_name as emp_name2_1_0_, employee0_.emp_salary as emp_sala3_1_0_, address1_.emp_id as emp_id1_0_1_, address1_.address_line1 as address_2_0_1_, address1_.city as city3_0_1_, address1_.zipcode as zipcode4_0_1_ from EMPLOYEE employee0_ left outer join ADDRESS address1_ on employee0_.emp_id=address1_.emp_id where employee0_.emp_id=?
1:: Name=Pankaj, Zipcode=95129
***** 1 *****
Fetch Count=1
Second Level Hit Count=0
Second Level Miss Count=1
Second Level Put Count=2
2:: Name=Pankaj, Zipcode=95129
***** 2 *****
Fetch Count=1
Second Level Hit Count=0
Second Level Miss Count=1
Second Level Put Count=2
3:: Name=Pankaj, Zipcode=95129
***** 3 *****
Fetch Count=1
Second Level Hit Count=2
Second Level Miss Count=1
Second Level Put Count=2
Hibernate: select employee0_.emp_id as emp_id1_1_0_, employee0_.emp_name as emp_name2_1_0_, employee0_.emp_salary as emp_sala3_1_0_, address1_.emp_id as emp_id1_0_1_, address1_.address_line1 as address_2_0_1_, address1_.city as city3_0_1_, address1_.zipcode as zipcode4_0_1_ from EMPLOYEE employee0_ left outer join ADDRESS address1_ on employee0_.emp_id=address1_.emp_id where employee0_.emp_id=?
4:: Name=Lisa, Zipcode=560100
***** 4 *****
Fetch Count=2
Second Level Hit Count=2
Second Level Miss Count=2
Second Level Put Count=4
5:: Name=Pankaj, Zipcode=95129
***** 5 *****
Fetch Count=2
Second Level Hit Count=4
Second Level Miss Count=2
Second Level Put Count=4
Wie Sie aus der Ausgabe sehen können, waren die Statistiken zuerst deaktiviert, aber wir haben sie aktiviert, um unseren Hibernate-Cache der zweiten Ebene zu überprüfen. Die schrittweise Erklärung der Ausgabe ist wie folgt:
- Bevor wir Daten in unserer Anwendung laden, sind alle Statistiken wie erwartet 0.
- Wenn wir den Mitarbeiter mit der ID=1 zum ersten Mal laden, wird er zuerst im Cache der ersten Ebene und dann im Cache der zweiten Ebene gesucht. Wenn er nicht im Cache gefunden wird, wird die Datenbankabfrage ausgeführt und die Abrufanzahl wird daher 1. Sobald das Objekt geladen ist, wird es sowohl im Cache der ersten Ebene als auch im Cache der zweiten Ebene gespeichert. Daher bleibt die Trefferanzahl der zweiten Ebene bei 0 und die Fehlanzahl wird 1. Beachten Sie, dass die Speicheranzahl 2 beträgt, denn das Mitarbeiterobjekt besteht auch aus einer Adresse, sodass beide Objekte im Cache der zweiten Ebene gespeichert werden und die Anzahl auf 2 erhöht wird.
- Als nächstes laden wir erneut den Mitarbeiter mit der ID=1, diesmal ist er im Cache der ersten Ebene vorhanden. Daher sehen Sie keine Datenbankabfrage und alle anderen Statistiken des Cache der zweiten Ebene bleiben ebenfalls gleich.
- Als nächstes verwenden wir die Methode
evict()
, um das Mitarbeiterobjekt aus dem Cache der ersten Ebene zu entfernen. Wenn wir es jetzt laden möchten, findet Hibernate es im Cache der zweiten Ebene. Deshalb wird keine Datenbankabfrage ausgeführt und die Abrufanzahl bleibt bei 1. Beachten Sie, dass die Trefferanzahl von 0 auf 2 steigt, da sowohl das Mitarbeiter- als auch das Adressobjekt aus dem Cache der zweiten Ebene gelesen werden. Die Anzahl der Cache-Fehltreffer und Einfügungen bleibt unverändert. - Als nächstes laden wir einen Mitarbeiter mit der ID 3. Es wird eine Datenbankabfrage ausgeführt und die Abrufanzahl erhöht sich auf 2, die Anzahl der Cache-Fehltreffer von 1 auf 2 und die Anzahl der Einfügungen von 2 auf 4.
- Als nächstes versuchen wir, einen Mitarbeiter mit der ID 1 in einer anderen Sitzung zu laden. Da der Hibernate-Cache der zweiten Ebene über Sitzungen hinweg gemeinsam genutzt wird, wird er im Cache der zweiten Ebene gefunden und es wird keine Datenbankabfrage ausgeführt. Die Abrufanzahl, die Anzahl der Cache-Fehltreffer und die Anzahl der Einfügungen bleiben gleich, während die Trefferanzahl von 2 auf 4 steigt.
Es ist also klar, dass unser Hibernate-Cache der zweiten Ebene, der EHCache von Hibernate, einwandfrei funktioniert. Hibernate-Statistiken helfen dabei, den Engpass im System zu finden und zu optimieren, um die Abrufanzahl zu reduzieren und mehr Daten aus dem Cache zu laden. Das war alles für das Hibernate EHCache-Beispiel. Ich hoffe, es wird Ihnen helfen, EHCache in Ihren Hibernate-Anwendungen zu konfigurieren und eine bessere Leistung durch den Hibernate-Cache der zweiten Ebene zu erzielen. Sie können das Beispielprojekt über den untenstehenden Link herunterladen und weitere Statistikdaten verwenden, um mehr zu lernen.
Source:
https://www.digitalocean.com/community/tutorials/hibernate-ehcache-hibernate-second-level-cache