Das Hibernate Second Level Cache Beispiel-Tutorial
Willkommen beim Hibernate Second Level Cache Beispiel-Tutorial. Heute werden wir uns Hibernate EHCache ansehen, den beliebtesten Anbieter von Hibernate Second Level Cache.
Hibernate Second Level Cache
Einer der größten Vorteile der Verwendung von Hibernate in großen Anwendungen ist die Unterstützung für Cache, wodurch Datenbankabfragen reduziert und die Leistung verbessert werden. Im vorherigen Beispiel haben wir uns den Hibernate First Level Cache angesehen, und heute werden wir den Hibernate Second Level Cache mit der Implementierung von Hibernate EHCache betrachten. Zu den Anbietern des Hibernate Second Level Cache 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 die verschiedenen Strategien zur Zwischenspeicherung eines Objekts kennen.
Read Only
Diese Cache-Strategie sollte für persistente Objekte verwendet werden, die immer gelesen, aber nie aktualisiert werden. Sie eignet sich gut zum Lesen und Zwischenspeichern von Anwendungs-Konfigurationen und anderen statischen Daten, die nie aktualisiert werden. Dies ist die einfachste Strategie mit der besten Leistung, da es keine Überprüfung gibt, ob das Objekt in der Datenbank aktualisiert wurde oder nicht.
Read Write
Sie eignet sich 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 dies nicht wissen, und die Daten könnten veraltet sein. Stellen Sie daher bei der Verwendung dieser Strategie sicher, dass Sie die Hibernate-API zum Aktualisieren der Daten verwenden.
Nonrestricted Read Write
Wenn die Anwendung nur gelegentlich Daten aktualisieren muss und keine strikte Transaktionsisolierung erforderlich ist, könnte ein Cache mit nicht-striktem Lese-Schreib-Zugriff geeignet sein.
Transactional
Die Transaktions-Cache-Strategie bietet Unterstützung für vollständig transaktionale Cache-Anbieter 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 einen Second Level Cache in Hibernate suchen. Ich werde nicht zu sehr ins Detail gehen, mein Hauptaugenmerk wird darauf liegen, es für die Hibernate-Anwendung zum Laufen zu bringen. Erstellen Sie ein Maven-Projekt in Eclipse oder Ihrer bevorzugten IDE. Die endgültige Implementierung sieht aus wie im folgenden Bild.
Hibernate EHCache Maven-Abhängigkeiten
Für den Hibernate Second Level Cache müssen wir die Abhängigkeiten ehcache-core
und hibernate-ehcache
in unsere Anwendung einfügen. EHCache verwendet slf4j
für das Logging, daher habe ich auch slf4j-simple
für die Protokollierungszwecke hinzugefügt. Ich verwende die neuesten Versionen aller dieser APIs. Es besteht eine geringe Wahrscheinlichkeit, 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 folgt 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 zu den Hibernate Second Level Cache Konfigurationen sind:
- hibernate.cache.region.factory_class: Diese Eigenschaft definiert die Factory-Klasse für den Second Level Cache. Ich verwende
org.hibernate.cache.ehcache.EhCacheRegionFactory
. Wenn Sie eine Singleton-Factory verwenden möchten, 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: Diese Eigenschaft aktiviert den Second Level Cache.
- hibernate.cache.use_query_cache: Diese Eigenschaft aktiviert den Abfrage-Cache. Ohne diese Einstellung werden HQL-Abfrageergebnisse nicht zwischengespeichert.
- net.sf.ehcache.configurationResourceName: Diese Eigenschaft definiert den Speicherort der EHCache-Konfigurationsdatei. Es handelt sich um eine optionale Eigenschaft. Wenn sie nicht angegeben ist, sucht EHCache standardmäßig nach der Datei
ehcache.xml
im Klassenpfad der Anwendung.
Hibernate EHCache Konfigurationsdatei
Unsere EHCache-Konfigurationsdatei myehcache.xml
sieht wie folgt 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
EHCache bietet viele Konfigurationsmöglichkeiten. Ich werde nicht zu sehr ins Detail gehen, aber einige der oben genannten wichtigen Konfigurationen sind:
- diskStore: EHCache speichert Daten im Speicher, aber wenn dieser überläuft, beginnt es, Daten auf das Dateisystem zu schreiben. Diese Eigenschaft wird verwendet, um den Speicherort zu definieren, an dem EHCache die überlaufenden Daten schreibt.
- defaultCache: Dies ist eine obligatorische Konfiguration. Sie wird verwendet, wenn ein Objekt zwischengespeichert werden soll und keine Caching-Bereiche dafür definiert sind.
- cache name=“employee“: Wir verwenden das Cache-Element, um den Bereich und seine Konfigurationen zu definieren. Es können mehrere Bereiche und deren Eigenschaften definiert werden. Während wir die Cache-Eigenschaften für Modellklassen definieren, können wir auch Bereiche mit Caching-Strategien definieren. Die Cache-Eigenschaften sind anhand der Namen leicht zu verstehen und klar.
- Cache-Bereiche
org.hibernate.cache.internal.StandardQueryCache
undorg.hibernate.cache.spi.UpdateTimestampsCache
sind definiert, da EHCache dafür Warnungen ausgegeben hat.
Hibernate EHCache – Model Bean Caching Strategie
Wir verwenden die Annotation org.hibernate.annotations.Cache
, um die Caching-Konfiguration bereitzustellen. Mit org.hibernate.annotations.CacheConcurrencyStrategy
definieren wir die Caching-Strategie und können den zu verwendenden Cache-Bereich für die Modellklassen festlegen.
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;
}
}
Hibernate SessionFactory Utility Class
Wir haben eine einfache Utility-Klasse, um Hibernate zu konfigurieren und die Singleton-Instanz von 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 {
// Create the SessionFactory from 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. Schreiben wir ein einfaches Programm, 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"));
//Initialize Sessions
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);
//clear first level cache, so that second level cache is used
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);
//Release resources
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 Statistiken über die Hibernate SessionFactory. Wir verwenden es, um die Abrufanzahl sowie die Treffer-, Verfehlen- und Einfügenanzahl des Second Level Cache anzuzeigen. Statistiken sind standardmäßig deaktiviert, um die Leistung zu verbessern. Deshalb aktivieren wir sie am Anfang des Programms. Ein Beispiel-Ausgabe sieht wie folgt 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
Überblick über die Statistiken des EHCache Testprogramms
Wie Sie aus der Ausgabe sehen können, waren die Statistiken zunächst deaktiviert, aber wir haben sie aktiviert, um unseren Hibernate Second Level Cache zu überprüfen. Eine schrittweise Erklärung der Ausgabe ist wie folgt:
- Bevor wir Daten in unsere Anwendung laden, sind alle Statistiken erwartungsgemäß auf 0 gesetzt.
- Wenn wir den Mitarbeiter mit der ID=1 zum ersten Mal laden, wird zuerst im First Level Cache und dann im Second Level Cache gesucht. Falls er nicht im Cache zu finden ist, wird eine Datenbankabfrage ausgeführt, und die Abrufanzahl erhöht sich auf 1. Sobald das Objekt geladen ist, ist es sowohl im First Level Cache als auch im Second Level Cache gespeichert. Daher bleibt die Trefferanzahl des Second Level Cache bei 0 und die Fehlanzahl ist 1. Beachten Sie, dass die Einfügenanzahl 2 beträgt, da das Mitarbeiterobjekt auch eine Adresse enthält, sodass beide Objekte im Second Level Cache gespeichert sind und die Anzahl auf 2 erhöht ist.
- Als Nächstes laden wir erneut den Mitarbeiter mit der ID=1. Dieses Mal befindet er sich im First Level Cache. Daher ist keine Datenbankabfrage ausgeführt, und alle anderen Statistiken des Second Level Cache bleiben unverändert.
- Als Nächstes verwenden wir die Methode
evict()
, um das Mitarbeiterobjekt aus dem First Level Cache zu entfernen. Wenn wir es nun laden, findet Hibernate es im Second Level Cache. Daher 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 Second Level Cache gelesen werden. Fehl- und Einfügenanzahl bleiben unverändert. - Als Nächstes laden wir einen Mitarbeiter mit der ID=3. Eine Datenbankabfrage wird ausgeführt, die Abrufanzahl erhöht sich auf 2, die Fehlanzahl steigt von 1 auf 2, und die Einfügenanzahl erhöht sich von 2 auf 4.
- Als Nächstes versuchen wir, den Mitarbeiter mit der ID=1 in einer anderen Sitzung zu laden. Da der Hibernate Second Level Cache zwischen Sitzungen geteilt wird, wird er im Second Level Cache gefunden, und keine Datenbankabfrage wird ausgeführt. Die Abruf-, Fehl- und Einfügenanzahl bleiben gleich, während die Trefferanzahl von 2 auf 4 steigt.
Fazit und Nutzen
Daraus wird deutlich, dass unser Hibernate Second Level Cache; Hibernate EHCache; ordnungsgemäß funktioniert. Hibernate-Statistiken sind hilfreich, um Engpässe im System zu identifizieren 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 hilft Ihnen, EHCache in Ihren Hibernate-Anwendungen zu konfigurieren und eine bessere Leistung durch den Hibernate Second Level Cache zu erzielen.