Hibernate EHCache – Cache de Segundo Nível do Hibernate

Bem-vindo ao Tutorial Exemplo do Hibernate Second Level Cache. Hoje, vamos nos aprofundar no Hibernate EHCache, que é o provedor de Cache de Segundo Nível do Hibernate mais popular.

Hibernate Second Level Cache

Um dos principais benefícios de usar o Hibernate em grandes aplicações é o suporte para cache, reduzindo assim consultas ao banco de dados e melhorando o desempenho. Em um exemplo anterior, analisamos o Hibernate First Level Cache e hoje vamos explorar o Cache de Segundo Nível do Hibernate usando a implementação Hibernate EHCache. Os provedores de cache de segundo nível do Hibernate incluem o EHCache e o Infinispan, mas o EHCache é mais popular e será utilizado em nosso projeto exemplo. No entanto, antes de prosseguirmos com nosso projeto, devemos conhecer as diferentes estratégias para o cache de um objeto.

  1. Read Only: Esta estratégia de cache deve ser usada para objetos persistentes que serão sempre lidos mas nunca atualizados. É bom para ler e fazer cache de configuração de aplicativos e outros dados estáticos que nunca são atualizados. Esta é a estratégia mais simples com o melhor desempenho, pois não há sobrecarga para verificar se o objeto foi atualizado no banco de dados ou não.
  2. Leitura e Escrita: É bom para objetos persistentes que podem ser atualizados pela aplicação Hibernate. No entanto, se os dados forem atualizados pelo backend ou outras aplicações, o Hibernate não terá conhecimento disso, e os dados podem ficar desatualizados. Portanto, ao usar essa estratégia, certifique-se de estar utilizando a API do Hibernate para atualizar os dados.
  3. Leitura e Escrita Não Restritas: Se a aplicação precisa ocasionalmente atualizar dados e não é necessária uma isolamento estrito de transação, um cache de leitura e escrita não restrito pode ser apropriado.
  4. Transacional: A estratégia de cache transacional oferece suporte a provedores de cache totalmente transacionais, como o JBoss TreeCache. Esse tipo de cache só pode ser usado em um ambiente JTA, e você deve especificar hibernate.transaction.manager_lookup_class.

Hibernate EHCache

Dado que o EHCache suporta todas as estratégias de cache mencionadas acima, é a melhor escolha ao procurar um cache de segundo nível no Hibernate. Não vou entrar em muitos detalhes sobre o EHCache; meu foco principal será fazê-lo funcionar para a aplicação Hibernate. Crie um projeto Maven no Eclipse ou em sua IDE favorita; a implementação final ficará semelhante à imagem abaixo. Vamos analisar cada componente da aplicação um por um.

Dependências do Maven do Hibernate EHCache

Para o cache de segundo nível do Hibernate, precisaríamos adicionar as dependências ehcache-core e hibernate-ehcache em nossa aplicação. O EHCache usa slf4j para logging, então também adicionei slf4j-simple para fins de logging. Estou usando as versões mais recentes de todas essas APIs, há uma pequena chance de que as APIs do hibernate-ehcache não sejam compatíveis com a API ehcache-core, nesse caso, você precisa verificar o pom.xml do hibernate-ehcache para descobrir a versão correta a ser usada. Nosso pom.xml final parece abaixo.

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

Configuração do Hibernate EHCache – Cache de Segundo Nível do Hibernate

O cache de segundo nível do Hibernate é desabilitado por padrão, então precisaríamos habilitá-lo e adicionar algumas configurações para fazê-lo funcionar. Nosso arquivo hibernate.cfg.xml parece abaixo.

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

Alguns pontos importantes sobre as configurações do cache de segundo nível do Hibernate são:

  1. hibernate.cache.region.factory_class é usado para definir a classe Factory para o armazenamento em cache de segundo nível. Estou usando org.hibernate.cache.ehcache.EhCacheRegionFactory para isso. Se você deseja que a classe da fábrica seja singleton, você deve usar a classe org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory. Se estiver usando o Hibernate 3, as classes correspondentes serão net.sf.ehcache.hibernate.EhCacheRegionFactory e net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory.
  2. hibernate.cache.use_second_level_cache é usado para habilitar o cache de segundo nível.
  3. hibernate.cache.use_query_cache é usado para habilitar o cache de consulta; sem isso, os resultados das consultas HQL não serão armazenados em cache.
  4. net.sf.ehcache.configurationResourceName é usado para definir a localização do arquivo de configuração do EHCache. É um parâmetro opcional e, se não estiver presente, o EHCache tentará localizar o arquivo ehcache.xml no classpath da aplicação.

Arquivo de Configuração do Hibernate EHCache

O nosso arquivo de configuração do EHCache, myehcache.xml, parece o seguinte.

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

O Hibernate EHCache fornece muitas opções. Não entrarei em muitos detalhes, mas algumas das configurações importantes estão acima:

  1. diskStore: O EHCache armazena dados na memória, mas quando começa a transbordar, começa a escrever dados no sistema de arquivos. Usamos esta propriedade para definir o local onde o EHCache escreverá os dados transbordados.
  2. defaultCache: É uma configuração obrigatória, é usada quando um objeto precisa ser armazenado em cache e não há regiões de cache definidas para isso.
  3. nome do cache=“funcionário”: Usamos o elemento de cache para definir a região e suas configurações. Podemos definir várias regiões e suas propriedades, enquanto definimos as propriedades de cache do modelo de beans, também podemos definir região com estratégias de cache. As propriedades de cache são fáceis de entender e claras com o nome.
  4. As regiões de cache org.hibernate.cache.internal.StandardQueryCache e org.hibernate.cache.spi.UpdateTimestampsCache são definidas porque o EHCache estava emitindo um aviso sobre isso.

Estratégia de Caching de Modelo de Beans do Cache de Segundo Nível do Hibernate

Usamos a anotação org.hibernate.annotations.Cache para fornecer a configuração de cache. org.hibernate.annotations.CacheConcurrencyStrategy é usado para definir a estratégia de cache e também podemos definir a região de cache a ser usada para os beans do modelo.

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

}

Note que estou usando a mesma configuração de banco de dados como no exemplo HQL em HQL, talvez queira verificar isso para criar as tabelas do banco de dados e carregar dados de exemplo.

Classe de Utilidade do Hibernate SessionFactory

Temos uma classe de utilidade simples para configurar o Hibernate e obter a instância única do 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 {
            // Cria a SessionFactory a partir do 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;
    }
}

O projeto de cache de segundo nível do Hibernate usando o Hibernate EHCache está pronto, vamos escrever um programa simples para testá-lo.

Programa de Teste do 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"));
		
		// Inicializar as sessões
		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);
		
		// Limpar o cache de primeiro nível, para que o cache de segundo nível seja usado
		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);
		
		// Liberar recursos
		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);
	}

}

O org.hibernate.stat.Statistics fornece as estatísticas da SessionFactory do Hibernate, estamos usando-o para imprimir a contagem de busca e as contagens de acerto, erro e inserção do cache de segundo nível. As estatísticas estão desativadas por padrão para melhor desempenho, por isso estou ativando-as no início do programa. Quando executamos o programa acima, obtemos muitas saídas geradas pelas APIs do Hibernate e EHCache, mas estamos interessados nos dados que estamos imprimindo. Uma execução de exemplo imprime a seguinte saída.

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

Como você pode ver na saída, as estatísticas estavam desativadas no início, mas as ativamos para verificar nosso cache de segundo nível do hibernate. A explicação passo a passo da saída é a seguinte:

  1. Antes de carregarmos quaisquer dados em nossa aplicação, todas as estatísticas são 0 como esperado.
  2. Quando estamos carregando o Funcionário com id=1 pela primeira vez, ele é primeiro pesquisado no cache de primeiro nível e depois no cache de segundo nível. Se não encontrado no cache, a consulta ao banco de dados é executada e, portanto, a contagem de busca se torna 1. Uma vez que o objeto é carregado, ele é salvo tanto no cache de primeiro nível quanto no cache de segundo nível. Portanto, a contagem de acertos no nível secundário permanece 0 e a contagem de erros se torna 1. Observe que a contagem de inserções é 2, isso ocorre porque o objeto Funcionário consiste também de um Endereço, então ambos os objetos são salvos no cache de segundo nível e a contagem é aumentada para 2.
  3. Em seguida, estamos carregando novamente o funcionário com id=1, desta vez ele está presente no cache de primeiro nível. Portanto, você não vê nenhuma consulta ao banco de dados e todas as outras estatísticas do cache de segundo nível também permanecem as mesmas.
  4. A seguir, estamos utilizando o método evict() para remover o objeto do funcionário do cache de primeiro nível. Agora, quando tentamos carregá-lo, o Hibernate o encontra no cache de segundo nível. Por isso, nenhuma consulta ao banco de dados é realizada e a contagem de busca permanece em 1. Observe que a contagem de acertos passa de 0 para 2, pois tanto os objetos Employee quanto Address são lidos do cache de segundo nível. A contagem de falta e a contagem de inserção no cache permanecem com os valores anteriores.
  5. Em seguida, estamos carregando um funcionário com id=3, a consulta ao banco de dados é executada e a contagem de busca aumenta para 2, a contagem de falta aumenta de 1 para 2 e a contagem de inserção no cache aumenta de 2 para 4.
  6. Depois, estamos tentando carregar um funcionário com id=1 em outra sessão. Como o cache de segundo nível do Hibernate é compartilhado entre sessões, ele é encontrado no cache de segundo nível e nenhuma consulta ao banco de dados é executada. A contagem de busca, a contagem de falta e a contagem de inserção no cache permanecem iguais, enquanto a contagem de acertos aumenta de 2 para 4.

Portanto, fica claro que nosso cache de segundo nível do Hibernate, o Hibernate EHCache, está funcionando corretamente. As estatísticas do Hibernate são úteis para encontrar gargalos no sistema e otimizá-lo para reduzir a contagem de buscas e carregar mais dados do cache. Isso é tudo para o exemplo do Hibernate EHCache. Espero que isso ajude você a configurar o EHCache em suas aplicações Hibernate e obter melhor desempenho por meio do cache de segundo nível do Hibernate. Você pode baixar o projeto de exemplo no link abaixo e usar outros dados estatísticos para aprender mais.

Baixe o Projeto Hibernate EHCache

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