Benvenuti al Tutorial sull’Esempio di Secondo Livello di Cache di Hibernate. Oggi esamineremo Hibernate EHCache, che è il provider di cache di secondo livello di Hibernate più popolare.
Cache di Secondo Livello di Hibernate
Uno dei principali vantaggi nell’utilizzare Hibernate in un’applicazione di grandi dimensioni è il suo supporto per la cache, che riduce quindi le query al database e migliora le prestazioni. Nell’esempio precedente, abbiamo esaminato la Cache di Primo Livello di Hibernate e oggi esamineremo la Cache di Secondo Livello di Hibernate utilizzando l’implementazione di Hibernate EHCache. I provider di cache di secondo livello di Hibernate includono EHCache e Infinispan, ma EHCache è più popolare e lo utilizzeremo per il nostro progetto di esempio. Tuttavia, prima di passare al nostro progetto, dovremmo conoscere le diverse strategie per la memorizzazione nella cache di un oggetto.
- Solo Lettura: Questa strategia di memorizzazione nella cache dovrebbe essere utilizzata per gli oggetti persistenti che verranno sempre letti ma mai aggiornati. È utile per la lettura e la memorizzazione nella cache della configurazione dell’applicazione e di altri dati statici che non vengono mai aggiornati. Questa è la strategia più semplice con le migliori prestazioni perché non c’è sovraccarico per verificare se l’oggetto è stato aggiornato nel database o meno.
- Leggere Scrivere: È buono per gli oggetti persistenti che possono essere aggiornati dall’applicazione Hibernate. Tuttavia, se i dati vengono aggiornati tramite il backend o altre applicazioni, Hibernate non ne sarà a conoscenza e i dati potrebbero diventare obsoleti. Quindi, mentre si utilizza questa strategia, assicurarsi di utilizzare l’API di Hibernate per aggiornare i dati.
- Nonrestricto Leggi Scrivi: Se l’applicazione ha solo bisogno occasionalmente di aggiornare i dati e non è richiesta un’isolamento rigoroso delle transazioni, una cache di tipo nonstrict-read-write potrebbe essere appropriata.
- Transazionale: La strategia di cache transazionale fornisce supporto per i fornitori di cache completamente transazionali come JBoss TreeCache. Tale cache può essere utilizzata solo in un ambiente JTA e è necessario specificare hibernate.transaction.manager_lookup_class.
Hibernate EHCache
Dato che EHCache supporta tutte le strategie di cache sopra indicate, è la scelta migliore quando si cerca una cache di secondo livello in Hibernate. Non mi addentrerò troppo nei dettagli su EHCache, il mio principale obiettivo sarà farlo funzionare per l’applicazione Hibernate. Crea un progetto Maven in Eclipse o nel tuo IDE preferito, l’implementazione finale avrà un aspetto simile all’immagine sottostante. Esaminiamo ciascun componente dell’applicazione uno per uno.
Dependency Maven di Hibernate EHCache
Per la cache di secondo livello di Hibernate, dovremmo aggiungere le dipendenze ehcache-core e hibernate-ehcache nella nostra applicazione. EHCache utilizza slf4j per il logging, quindi ho anche aggiunto slf4j-simple per scopi di logging. Sto utilizzando le versioni più recenti di tutte queste API, c’è una piccola possibilità che le API di hibernate-ehcache non siano compatibili con l’API ehcache-core, in tal caso è necessario controllare il pom.xml di hibernate-ehcache per scoprire la versione corretta da utilizzare. Il nostro pom.xml finale appare come segue.
<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>
Configurazione della Cache di Secondo Livello di Hibernate EHCache
La cache di secondo livello di Hibernate è disabilitata per impostazione predefinita, quindi dovremmo abilitarla e aggiungere alcune configurazioni per farla funzionare. Il nostro file hibernate.cfg.xml appare come segue.
<?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>
Alcuni punti importanti sulle configurazioni della cache di secondo livello di Hibernate sono:
- hibernate.cache.region.factory_class viene utilizzato per definire la classe Factory per la memorizzazione nella cache di secondo livello, sto utilizzando
org.hibernate.cache.ehcache.EhCacheRegionFactory
per questo. Se vuoi che la classe factory sia singleton, dovresti utilizzare la classeorg.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
. Se stai usando Hibernate 3, le classi corrispondenti sarannonet.sf.ehcache.hibernate.EhCacheRegionFactory
enet.sf.ehcache.hibernate.SingletonEhCacheRegionFactory
. - hibernate.cache.use_second_level_cache viene utilizzato per abilitare la cache di secondo livello.
- hibernate.cache.use_query_cache viene utilizzato per abilitare la cache delle query, senza di essa i risultati delle query HQL non verranno memorizzati nella cache.
- net.sf.ehcache.configurationResourceName viene utilizzato per definire la posizione del file di configurazione EHCache, è un parametro opzionale e se non è presente EHCache cercherà di individuare il file ehcache.xml nel classpath dell’applicazione.
File di configurazione di Hibernate EHCache
Il nostro file di configurazione EHCache myehcache.xml appare come segue.
<?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>
La configurazione di Hibernate EHCache fornisce molte opzioni, non entrerò nei dettagli, ma alcune delle configurazioni importanti sopra sono:
- diskStore: EHCache memorizza i dati in memoria, ma quando inizia a traboccare, inizia a scrivere i dati nel file system. Utilizziamo questa proprietà per definire la posizione in cui EHCache scriverà i dati traboccati.
- defaultCache: È una configurazione obbligatoria, viene utilizzata quando un oggetto deve essere memorizzato nella cache e non sono definite regioni di caching per esso.
- nome-cache=“employee”: Utilizziamo l’elemento cache per definire la regione e le sue configurazioni. Possiamo definire molteplici regioni e le relative proprietà; mentre definiamo le proprietà della cache per i modelli di bean, possiamo anche definire la regione con le strategie di caching. Le proprietà della cache sono facili da capire e chiare con il nome.
- Le regioni della cache
org.hibernate.cache.internal.StandardQueryCache
eorg.hibernate.cache.spi.UpdateTimestampsCache
sono definite perché EHCache stava dando un avviso in merito.
Cache di secondo livello di Hibernate – Strategia di memorizzazione nella cache del modello di bean
Utilizziamo l’annotazione org.hibernate.annotations.Cache
per fornire la configurazione della cache. org.hibernate.annotations.CacheConcurrencyStrategy
è utilizzato per definire la strategia di memorizzazione nella cache e possiamo anche definire la regione della cache da utilizzare per i modelli di bean.
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;
}
}
Nota che sto usando lo stesso setup del database come nell’esempio HQL, potresti voler controllare quello per creare le tabelle del database e caricare dati di esempio.
Classe di Utilità SessionFactory di Hibernate
Abbiamo una semplice classe di utilità per configurare Hibernate e ottenere l’istanza singleton di SessionFactory
.
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 {
// Creare la SessionFactory da 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;
}
}
Il nostro progetto di cache di secondo livello Hibernate usando Hibernate EHCache è pronto, scriviamo un semplice programma per testarlo.
Programma di Test di Hibernate EHCache
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"));
// Inizializzare le Sessioni
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);
// Svuotare la cache di primo livello, in modo che venga utilizzata la cache di secondo livello
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);
// Rilasciare risorse
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);
}
}
Il org.hibernate.stat.Statistics
fornisce le statistiche della SessionFactory di Hibernate, lo stiamo utilizzando per stampare il conteggio degli accessi e la cache di secondo livello colpita, mancata e conteggio di inserimento. Le statistiche sono disabilitate per impostazione predefinita per una migliore performance, ecco perché le abilito all’inizio del programma. Quando eseguiamo il programma sopra, otteniamo molta output generato da Hibernate e dalle API di EHCache, ma siamo interessati ai dati che stiamo stampando. Una esecuzione di esempio stampa il seguente output.
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
Come puoi vedere dall’output, le statistiche erano disabilitate all’inizio ma le abbiamo abilitate per controllare la nostra cache di secondo livello di Hibernate. La spiegazione passo passo dell’output è la seguente:
- Prima di caricare qualsiasi dato nella nostra applicazione, tutte le statistiche sono 0 come previsto.
- Quando stiamo caricando l’Employee con id=1 per la prima volta, viene prima cercato nella cache di primo livello e poi nella cache di secondo livello. Se non trovato nella cache, viene eseguita la query al database e quindi il conteggio degli accessi diventa 1. Una volta che l’oggetto è caricato, viene salvato sia nella cache di primo livello che in quella di secondo livello. Quindi il conteggio di colpi di secondo livello rimane 0 e il conteggio di mancate diventa 1. Nota che il conteggio di inserimento è 2, questo perché l’oggetto Employee consiste anche di Address, quindi entrambi gli oggetti vengono salvati nella cache di secondo livello e il conteggio aumenta a 2.
- Successivamente, stiamo di nuovo caricando l’employee con id=1, questa volta è presente nella cache di primo livello. Quindi non vedi alcuna query al database e tutte le altre statistiche della cache di secondo livello rimangono uguali.
- Di seguito stiamo utilizzando il metodo
evict()
per rimuovere l’oggetto dell’impiegato dalla cache del primo livello, ora quando stiamo cercando di caricarlo, Hibernate lo trova nella cache del secondo livello. Ecco perché non viene eseguita alcuna query al database e il conteggio dei fetch rimane a 1. Si noti che il conteggio degli hit passa da 0 a 2 perché sia l’oggetto Employee che Address sono letti dalla cache del secondo livello. Il conteggio degli hit e dei put rimane invariato al valore precedente. - Successivamente stiamo caricando un impiegato con id=3, viene eseguita una query al database e il conteggio dei fetch aumenta a 2, il conteggio dei miss passa da 1 a 2 e il conteggio dei put passa da 2 a 4.
- Successivamente stiamo cercando di caricare l’impiegato con id=1 in un’altra sessione, poiché la cache del secondo livello di Hibernate è condivisa tra le sessioni, viene trovata nella cache del secondo livello e non viene eseguita alcuna query al database. Il conteggio dei fetch, dei miss e dei put rimane invariato, mentre il conteggio degli hit aumenta da 2 a 4.
Quindi è chiaro che la nostra cache del secondo livello di Hibernate, Hibernate EHCache, sta funzionando correttamente. Le statistiche di Hibernate sono utili per trovare il collo di bottiglia nel sistema e ottimizzarlo per ridurre il conteggio dei fetch e caricare più dati dalla cache. Questo è tutto per l’esempio di Hibernate EHCache, spero che ti aiuti a configurare EHCache nelle tue applicazioni Hibernate e ottenere migliori prestazioni tramite la cache del secondo livello di Hibernate. Puoi scaricare il progetto di esempio dal seguente link e utilizzare altri dati statistici per apprendere di più.
Source:
https://www.digitalocean.com/community/tutorials/hibernate-ehcache-hibernate-second-level-cache