Hibernate EHCache – Hibernate Second Level Cache

Bienvenue dans le tutoriel sur l’exemple d’utilisation du cache de deuxième niveau avec Hibernate. Aujourd’hui, nous allons examiner Hibernate EHCache, qui est le fournisseur de cache de deuxième niveau le plus populaire.

Cache de deuxième niveau Hibernate

L’un des principaux avantages de l’utilisation de Hibernate dans une grande application est son support du cache, ce qui réduit les requêtes à la base de données et améliore les performances. Dans l’exemple précédent, nous avons examiné le Cache de premier niveau Hibernate, et aujourd’hui, nous allons explorer le cache de deuxième niveau Hibernate en utilisant Hibernate EHCache. Les fournisseurs de cache de deuxième niveau dans Hibernate incluent EHCache et Infinispan, mais EHCache est plus populaire, et c’est celui que nous utiliserons dans notre projet exemple. Cependant, avant de passer à notre projet, nous devrions connaître les différentes stratégies pour mettre en cache un objet.

  1. En lecture seule : Cette stratégie de mise en cache doit être utilisée pour les objets persistants qui seront toujours lus mais jamais mis à jour. C’est idéal pour la lecture et la mise en cache de la configuration de l’application et d’autres données statiques qui ne sont jamais mises à jour. Il s’agit de la stratégie la plus simple avec les meilleures performances, car il n’y a pas de surcharge pour vérifier si l’objet a été mis à jour dans la base de données ou non.
  2. Lecture Écriture: C’est bon pour les objets persistants qui peuvent être mis à jour par l’application Hibernate. Cependant, si les données sont mises à jour soit par le backend soit par d’autres applications, Hibernate n’en saura rien et les données pourraient être obsolètes. Donc, lors de l’utilisation de cette stratégie, assurez-vous d’utiliser l’API Hibernate pour mettre à jour les données.
  3. Lecture Écriture Non Restreinte: Si l’application doit occasionnellement mettre à jour les données et qu’une isolation de transaction stricte n’est pas nécessaire, un cache de lecture-écriture non strict pourrait être approprié.
  4. Transactionnel: La stratégie de cache transactionnel fournit un support pour les fournisseurs de cache entièrement transactionnels tels que JBoss TreeCache. Un tel cache ne peut être utilisé que dans un environnement JTA et vous devez spécifier hibernate.transaction.manager_lookup_class.

Hibernate EHCache

Puisque EHCache prend en charge toutes les stratégies de cache mentionnées ci-dessus, c’est le meilleur choix lorsque vous recherchez un cache de deuxième niveau dans Hibernate. Je ne rentrerai pas dans les détails d’EHCache, mon objectif principal sera de le faire fonctionner pour l’application Hibernate. Créez un projet Maven dans Eclipse ou votre IDE préféré, l’implémentation finale ressemblera à l’image ci-dessous. Examinons chaque composant de l’application un par un.

Dépendances Maven pour Hibernate EHCache

Pour le cache de niveau 2 d’hibernate, nous devons ajouter les dépendances ehcache-core et hibernate-ehcache dans notre application. EHCache utilise slf4j pour le journalisation, donc j’ai également ajouté slf4j-simple à des fins de journalisation. J’utilise les versions les plus récentes de toutes ces API. Il y a une légère chance que les API hibernate-ehcache ne soient pas compatibles avec l’API ehcache-core, dans ce cas, vous devez vérifier le fichier pom.xml de hibernate-ehcache pour trouver la version correcte à utiliser. Notre fichier pom.xml final ressemble à ceci.

<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>

Configuration du Cache de Niveau 2 d’Hibernate EHCache

Le cache de niveau 2 d’hibernate est désactivé par défaut, nous devons donc l’activer et ajouter quelques configurations pour le faire fonctionner. Notre fichier hibernate.cfg.xml ressemble à ceci.

<?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>

Quelques points importants concernant les configurations du cache de niveau 2 d’hibernate sont :

  1. hibernate.cache.region.factory_class est utilisé pour définir la classe Factory pour le caching de deuxième niveau, j’utilise org.hibernate.cache.ehcache.EhCacheRegionFactory pour cela. Si vous voulez que la classe de la factory soit un singleton, vous devriez utiliser la classe org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory. Si vous utilisez Hibernate 3, les classes correspondantes seront net.sf.ehcache.hibernate.EhCacheRegionFactory et net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory.
  2. hibernate.cache.use_second_level_cache est utilisé pour activer le caching de deuxième niveau.
  3. hibernate.cache.use_query_cache est utilisé pour activer le caching de requêtes, sans cela les résultats des requêtes HQL ne seront pas mis en cache.
  4. net.sf.ehcache.configurationResourceName est utilisé pour définir l’emplacement du fichier de configuration EHCache, c’est un paramètre optionnel et s’il n’est pas présent, EHCache essaiera de localiser le fichier ehcache.xml dans le classpath de l’application.

Fichier de configuration Hibernate EHCache

Notre fichier de configuration EHCache myehcache.xml ressemble à ceci.

<?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 configuration EHCache de Hibernate offre de nombreuses options, je ne vais pas entrer dans les détails, mais certaines des configurations importantes ci-dessus sont:

  1. diskStore: EHCache stocke les données en mémoire mais lorsqu’elle commence à déborder, elle commence à écrire des données dans le système de fichiers. Nous utilisons cette propriété pour définir l’emplacement où EHCache écrira les données débordées.
  2. defaultCache: C’est une configuration obligatoire, elle est utilisée lorsqu’un objet doit être mis en cache et qu’il n’y a pas de régions de mise en cache définies pour cela.
  3. nom du cache=“employé”: Nous utilisons l’élément de cache pour définir la région et ses configurations. Nous pouvons définir plusieurs régions et leurs propriétés, tout en définissant les propriétés de mise en cache des beans de modèle, nous pouvons également définir une région avec des stratégies de mise en cache. Les propriétés de mise en cache sont faciles à comprendre et claires avec le nom.
  4. Les régions de cache org.hibernate.cache.internal.StandardQueryCache et org.hibernate.cache.spi.UpdateTimestampsCache sont définies parce qu’EHCache émettait un avertissement à ce sujet.

Stratégie de mise en cache du modèle de deuxième niveau Hibernate

Nous utilisons l’annotation org.hibernate.annotations.Cache pour fournir la configuration de mise en cache. org.hibernate.annotations.CacheConcurrencyStrategy est utilisé pour définir la stratégie de mise en cache et nous pouvons également définir la région de cache à utiliser pour les beans de modèle.

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;
	}

}

Notez que j’utilise la même configuration de base de données que dans l’exemple HQL, vous voudrez peut-être vérifier cela pour créer les tables de la base de données et charger des données d’exemple.

Classe utilitaire Hibernate SessionFactory

Nous avons une classe utilitaire simple pour configurer Hibernate et obtenir l’instance unique de la 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 {
            // Créer la SessionFactory à partir de 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;
    }
}

Notre projet de cache de deuxième niveau Hibernate utilisant Hibernate EHCache est prêt, écrivons un programme simple pour le tester.

Programme de test 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"));
		
		// Initialiser les 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);
		
		// Effacer le cache de premier niveau, afin que le cache de deuxième niveau soit utilisé
		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);
		
		// Libérer les ressources
		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 fournit les statistiques de Hibernate SessionFactory, nous l’utilisons pour afficher le nombre de récupérations et les statistiques de mise en cache de niveau 2, les ratés et les mises en cache réussies. Les statistiques sont désactivées par défaut pour de meilleures performances, c’est pourquoi je les active au début du programme. Lorsque nous exécutons le programme ci-dessus, nous obtenons beaucoup de sorties générées par les API Hibernate et EHCache, mais nous nous intéressons aux données que nous imprimons. Une exécution d’exemple imprime la sortie suivante.

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

Comme vous pouvez le voir dans la sortie, les statistiques étaient désactivées au début, mais nous les avons activées pour vérifier notre deuxième cache de niveau Hibernate. L’explication étape par étape de la sortie est la suivante :

  1. Avant de charger des données dans notre application, toutes les statistiques sont à 0 comme prévu.
  2. Lorsque nous chargeons l’employé avec l’ID=1 pour la première fois, il est d’abord recherché dans le cache de niveau 1, puis dans le cache de niveau 2. S’il n’est pas trouvé dans le cache, une requête de base de données est exécutée et le compteur de récupération devient 1. Une fois l’objet chargé, il est enregistré à la fois dans le cache de niveau 1 et dans le cache de niveau 2. Ainsi, le compteur d’accès de niveau 2 reste à 0 et le compteur de ratés devient 1. Notez que le compteur d’enregistrement est de 2, car l’objet Employé comprend également l’adresse, donc les deux objets sont enregistrés dans le cache de niveau 2 et le compteur est augmenté à 2.
  3. Ensuite, nous chargeons à nouveau l’employé avec l’ID=1, cette fois il est présent dans le cache de niveau 1. Vous ne voyez donc aucune requête de base de données et toutes les autres statistiques du cache de niveau 2 restent inchangées.
  4. Ensuite, nous utilisons la méthode evict() pour supprimer l’objet employé du cache de premier niveau. Maintenant, lors de la tentative de chargement, Hibernate le trouve dans le cache de deuxième niveau. C’est pourquoi aucune requête n’est envoyée à la base de données et le nombre d’objets récupérés reste à 1. Notez que le nombre d’accès passe de 0 à 2 car les objets Employee et Address sont lus à partir du cache de deuxième niveau. Le nombre d’accès manqués et le nombre d’ajouts restent inchangés.
  5. Ensuite, nous chargeons un employé avec l’identifiant 3, une requête de base de données est exécutée et le nombre d’objets récupérés passe à 2, le nombre d’accès manqués passe de 1 à 2 et le nombre d’ajouts passe de 2 à 4.
  6. Ensuite, nous tentons de charger l’employé avec l’identifiant 1 dans une autre session. Étant donné que le cache de deuxième niveau d’Hibernate est partagé entre les sessions, il est trouvé dans le cache de deuxième niveau et aucune requête de base de données n’est exécutée. Le nombre d’objets récupérés, le nombre d’accès manqués et le nombre d’ajouts restent inchangés, tandis que le nombre d’accès augmente de 2 à 4.

Il est donc clair que notre cache de deuxième niveau Hibernate, Hibernate EHCache, fonctionne correctement. Les statistiques d’Hibernate sont utiles pour trouver les goulots d’étranglement dans le système et les optimiser afin de réduire le nombre d’objets récupérés et de charger plus de données depuis le cache. C’est tout pour l’exemple Hibernate EHCache. J’espère que cela vous aidera à configurer EHCache dans vos applications Hibernate et à obtenir de meilleures performances grâce au cache de deuxième niveau Hibernate. Vous pouvez télécharger le projet d’exemple depuis le lien ci-dessous et utiliser d’autres données statistiques pour en savoir plus.

Télécharger le projet Hibernate EHCache

Source:
https://www.digitalocean.com/community/tutorials/hibernate-ehcache-hibernate-second-level-cache