Hibernate EHCache – Hibernate Tweede Niveau Cache

Welkom bij de Hibernate Second Level Cache Example Tutorial. Vandaag zullen we kijken naar Hibernate EHCache, dat de meest populaire Hibernate Second Level Cache-provider is.

Hibernate Second Level Cache

Een van de belangrijkste voordelen van het gebruik van Hibernate in grote toepassingen is de ondersteuning voor cache, waardoor databasequery’s worden verminderd en de prestaties worden verbeterd. In het vorige voorbeeld hebben we gekeken naar de Hibernate First Level Cache, en vandaag zullen we kijken naar Hibernate Second Level Cache met behulp van Hibernate EHCache-implementatie. Hibernate Second Level Cache-providers zijn onder andere EHCache en Infinispan, maar EHCache is populairder en dat zullen we gebruiken voor ons voorbeeldproject. Voordat we echter naar ons project gaan, moeten we de verschillende strategieën kennen voor het cachen van een object.

  1. Alleen Lezen: Deze cachingstrategie moet worden gebruikt voor persistente objecten die altijd worden gelezen maar nooit worden bijgewerkt. Het is goed voor het lezen en cachen van toepassingsconfiguraties en andere statische gegevens die nooit worden bijgewerkt. Dit is de eenvoudigste strategie met de beste prestaties omdat er geen overbelasting is om te controleren of het object in de database is bijgewerkt of niet.
  2. read write: het is goed voor persistente objecten die kunnen worden bijgewerkt door de hibernate-toepassing. als de gegevens echter worden bijgewerkt via de backend of andere toepassingen, zal hibernate daar niet van op de hoogte zijn en kunnen de gegevens verouderd zijn. dus bij het gebruik van deze strategie, zorg ervoor dat u de hibernate api gebruikt om de gegevens bij te werken.

  3. nonrestricted read write: als de toepassing slechts af en toe gegevens moet bijwerken en strikte transactie-isolatie niet vereist is, kan een nonstrict-read-write-cache geschikt zijn.
  4. transactional: de transactionele cache-strategie biedt ondersteuning voor volledig transactionele cache-providers zoals jboss treecache. zo’n cache kan alleen worden gebruikt in een jta-omgeving en u moet hibernate.transaction.manager_lookup_class specificeren.

hibernate ehcache

aangezien ehcache alle bovengenoemde cache-strategieën ondersteunt, is het de beste keuze wanneer u op zoek bent naar cache van het tweede niveau in hibernate. ik zal niet te veel ingaan op ehcache, mijn belangrijkste focus zal zijn om het te laten werken voor de hibernate-toepassing. maak een mavenproject in eclipse of uw favoriete ide, de uiteindelijke implementatie zal eruit zien als de onderstaande afbeelding. laten we elk onderdeel van de toepassing eens nader bekijken.

Hibernate EHCache Maven Dependencies

Voor de Hibernate tweede niveau cache, moeten we de afhankelijkheden ehcache-core en hibernate-ehcache toevoegen aan onze applicatie. EHCache gebruikt slf4j voor logging, dus ik heb ook slf4j-simple toegevoegd voor loggingdoeleinden. Ik gebruik de nieuwste versies van al deze API’s, er is een kleine kans dat de hibernate-ehcache API’s niet compatibel zijn met de ehcache-core API, in dat geval moet je de pom.xml van hibernate-ehcache controleren om de juiste versie te vinden om te gebruiken. Onze uiteindelijke pom.xml ziet er als volgt uit.

<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 Tweede Niveau Cache – Hibernate EHCache Configuratie

Hibernate Tweede niveau cache is standaard uitgeschakeld, dus we moeten het inschakelen en enkele configuraties toevoegen om het werkend te krijgen. Ons hibernate.cfg.xml-bestand ziet er als volgt uit.

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

Enkele belangrijke punten over de configuraties van de hibernate tweede niveau cache zijn:

  1. hibernate.cache.region.factory_class wordt gebruikt om de Factory-klasse voor caching op het tweede niveau te definiëren. Ik gebruik hiervoor org.hibernate.cache.ehcache.EhCacheRegionFactory. Als je wilt dat de factory-klasse een singleton is, moet je de klasse org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory gebruiken. Als je Hibernate 3 gebruikt, zijn de overeenkomstige klassen net.sf.ehcache.hibernate.EhCacheRegionFactory en net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory.
  2. hibernate.cache.use_second_level_cache wordt gebruikt om de caching op het tweede niveau in te schakelen.
  3. hibernate.cache.use_query_cache wordt gebruikt om de querycache in te schakelen. Zonder dit worden resultaten van HQL-query’s niet gecachet.
  4. net.sf.ehcache.configurationResourceName wordt gebruikt om de locatie van het configuratiebestand van EHCache te definiëren. Het is een optionele parameter en als deze niet aanwezig is, zal EHCache proberen het bestand ehcache.xml te vinden in het toepassingsklassenpad.

Configuratiebestand voor Hibernate EHCache

Ons EHCache-configuratiebestand myehcache.xml ziet er als volgt uit.

<?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 biedt veel opties. Ik zal niet te diep ingaan, maar enkele van de belangrijke configuraties hierboven zijn:

  1. diskStore: EHCache slaat gegevens op in het geheugen, maar wanneer het begint te overstromen, begint het gegevens naar het bestandssysteem te schrijven. We gebruiken deze eigenschap om de locatie te definiëren waar EHCache de overgelopen gegevens zal schrijven.
  2. defaultCache: Dit is een verplichte configuratie, het wordt gebruikt wanneer een object moet worden gecachet en er geen caching-regio’s zijn gedefinieerd voor dat object.
  3. cachenaam=”medewerker”: We gebruiken het cache-element om de regio en de configuraties ervan te definiëren. We kunnen meerdere regio’s en hun eigenschappen definiëren. Bij het definiëren van eigenschappen voor model beans caching, kunnen we ook de regio definiëren met caching-strategieën. De eigenschappen van de cache zijn gemakkelijk te begrijpen en duidelijk met de naam.
  4. Cacheregio’s org.hibernate.cache.internal.StandardQueryCache en org.hibernate.cache.spi.UpdateTimestampsCache zijn gedefinieerd omdat EHCache daarvoor een waarschuwing gaf.

Hibernate Tweede Niveau Cache – Model Bean Caching Strategie

We gebruiken de annotatie org.hibernate.annotations.Cache om de caching-configuratie te bieden. org.hibernate.annotations.CacheConcurrencyStrategy wordt gebruikt om de caching-strategie te definiëren, en we kunnen ook de cacheregio definiëren die moet worden gebruikt voor de model beans.

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

}

Let op dat ik dezelfde database-opstelling gebruik als in het HQL-voorbeeld, je wilt dat wellicht controleren om de database-tabellen aan te maken en voorbeeldgegevens te laden.

Hibernate SessionFactory Utility Class

We hebben een eenvoudige hulpprogramma klasse om Hibernate te configureren en de singleton-instantie van de SessionFactory te verkrijgen.

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 {
            // Creëer de SessionFactory vanuit 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;
    }
}

Ons Hibernate second-level-cacheproject met gebruik van Hibernate EHCache is gereed. Laten we een eenvoudig programma schrijven om het te testen.

Hibernate EHCache Testprogramma

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"));
		
		// Initialiseer 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);
		
		// Maak de first-level-cache leeg, zodat de second-level-cache wordt gebruikt
		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 biedt de statistieken van de Hibernate SessionFactory. We gebruiken het om het ophalen van gegevens en de treffers, missers en toevoegingen van de tweede cache laag te registreren. Statistieken zijn standaard uitgeschakeld voor betere prestaties, daarom schakel ik ze in aan het begin van het programma. Bij het uitvoeren van het bovenstaande programma genereren Hibernate en EHCache API’s veel output, maar we zijn alleen geïnteresseerd in de gegevens die we afdrukken. Een voorbeeldrun genereert de volgende 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

Zoals je kunt zien aan de output, waren statistieken aanvankelijk uitgeschakeld, maar we hebben ze ingeschakeld om onze Hibernate tweede cache te controleren. Stapsgewijze uitleg van de output is als volgt:

  1. Voordat we gegevens in onze toepassing laden, zijn alle statistieken 0, zoals verwacht.
  2. Wanneer we de Employee met id=1 voor de eerste keer laden, wordt deze eerst gezocht in de eerste cache en vervolgens in de tweede cache. Als het niet in de cache wordt gevonden, wordt de databasequery uitgevoerd en wordt het ophaalnummer dus 1. Zodra het object is geladen, wordt het opgeslagen in zowel de eerste als de tweede cache. Daarom blijft het aantal treffers in de tweede laag 0 en wordt het aantal missers 1. Merk op dat het toevoegingsaantal 2 is, omdat het Employee-object ook Address bevat. Dus beide objecten worden opgeslagen in de tweede cache en het aantal wordt verhoogd naar 2.
  3. Vervolgens laden we opnieuw de werknemer met id=1, deze keer is deze aanwezig in de eerste cache. Dus je ziet geen databasequery en alle andere statistieken van de tweede cache blijven hetzelfde.
  4. Volgende gebruiken we de evict() methode om het werknemersobject uit de first level cache te verwijderen, nu wanneer we het proberen te laden, vindt Hibernate het in de tweede level cache. Daarom wordt er geen databasequery uitgevoerd en blijft het ophaaltelling 1. Let op dat de hit-telling van 0 naar 2 gaat omdat zowel de Employee- als Address-objecten uit de tweede level cache worden gelezen. Tweede level miss en put-telling blijven op de eerdere waarde.
  5. Daarna laden we een werknemer met id=3, er wordt een databasequery uitgevoerd en de ophaaltelling neemt toe tot 2, de miss-telling neemt toe van 1 naar 2 en de put-telling neemt toe van 2 naar 4.
  6. Daarna proberen we een werknemer met id=1 in een andere sessie te laden. Omdat de Hibernate tweede level cache gedeeld is over sessies, wordt deze gevonden in de tweede level cache en wordt er geen databasequery uitgevoerd. Ophaaltelling, miss-telling en put-telling blijven hetzelfde, terwijl de hit-telling toeneemt van 2 naar 4.

Het is dus duidelijk dat onze Hibernate tweede level cache, Hibernate EHCache, goed werkt. Hibernate-statistieken helpen bij het vinden van de knelpunten in het systeem en optimaliseren ervan om de ophaaltelling te verminderen en meer gegevens uit de cache te laden. Dat is alles voor het Hibernate EHCache voorbeeld. Ik hoop dat het je zal helpen bij het configureren van EHCache in je Hibernate-toepassingen en het verkrijgen van een betere prestatie via de Hibernate tweede level cache. Je kunt het voorbeeldproject downloaden via onderstaande link en andere statistische gegevens gebruiken om meer te leren.

Download Hibernate EHCache Project

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