Поиск по сайту:

Hibernate EHCache — Кэш второго уровня Hibernate


Добро пожаловать в учебник по примерам кэша второго уровня Hibernate. Сегодня мы рассмотрим Hibernate EHCache, который является самым популярным поставщиком кэша Hibernate второго уровня.

Спящий режим кэша второго уровня

  1. Только чтение: эту стратегию кэширования следует использовать для постоянных объектов, которые всегда будут читаться, но никогда не обновляться. Это удобно для чтения и кэширования конфигурации приложений и других статических данных, которые никогда не обновляются. Это самая простая стратегия с наилучшей производительностью, поскольку нет перегрузки для проверки того, обновлен ли объект в базе данных или нет.
  2. Чтение-запись. Подходит для постоянных объектов, которые могут быть обновлены приложением, находящимся в спящем режиме. Однако, если данные обновляются либо через серверную часть, либо через другие приложения, то спящий режим никак не узнает об этом, и данные могут быть устаревшими. Поэтому при использовании этой стратегии убедитесь, что вы используете Hibernate API для обновления данных.
  3. Неограниченное чтение-запись. Если приложению требуется обновлять данные лишь изредка и строгая изоляция транзакций не требуется, может подойти нестрогий кэш-чтение-запись.
  4. Транзакционный. Стратегия транзакционного кэша обеспечивает поддержку поставщиков полностью транзакционного кэша, таких как JBoss TreeCache. Такой кеш можно использовать только в среде JTA, и вы должны указать hibernate.transaction.manager_lookup_class.

Спящий режим EHCache

Зависимости Hibernate EHCache Maven

Для спящего кеша второго уровня нам нужно будет добавить в наше приложение зависимости ehcache-core и hibernate-ehcache. EHCache использует slf4j для ведения журнала, поэтому я также добавил slf4j-simple для ведения журнала. Я использую последние версии всех этих API, есть небольшая вероятность того, что API hibernate-ehcache несовместимы с API ehcache-core, в этом случае вам нужно проверить pom.xml hibernate-ehcache, чтобы узнать правильная версия для использования. Наш окончательный файл pom.xml выглядит следующим образом.

<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 — конфигурация Hibernate EHCache

Кэш второго уровня Hibernate по умолчанию отключен, поэтому нам нужно включить его и добавить некоторые конфигурации, чтобы он заработал. Наш файл hibernate.cfg.xml выглядит так, как показано ниже.

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

Вот некоторые важные моменты, касающиеся конфигурации кэша второго уровня гибернации:

  1. hibernate.cache.region.factory_class используется для определения класса Factory для кэширования второго уровня. Для этого я использую org.hibernate.cache.ehcache.EhCacheRegionFactory. Если вы хотите, чтобы фабричный класс был одноэлементным, вам следует использовать класс org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory. Если вы используете Hibernate 3, соответствующими классами будут net.sf.ehcache.hibernate.EhCacheRegionFactory и net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory.
  2. hibernate.cache.use_second_level_cache используется для включения кэша второго уровня.
  3. hibernate.cache.use_query_cache используется для включения кэширования запросов, без него результаты запросов HQL не будут кэшироваться.
  4. net.sf.ehcache.configurationResourceName используется для определения местоположения файла конфигурации EHCache. Это необязательный параметр, и если он отсутствует, EHCache попытается найти файл ehcache.xml в пути к классам приложения.

Файл конфигурации Hibernate EHCache

Наш файл конфигурации EHCache myehcache.xml выглядит следующим образом.

<?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 предоставляет множество опций, я не буду вдаваться в подробности, но некоторые из важных конфигураций выше:

  1. diskStore: EHCache сохраняет данные в памяти, но когда она начинает переполняться, она начинает записывать данные в файловую систему. Мы используем это свойство, чтобы определить место, куда EHCache будет записывать переполненные данные.
  2. DefaultCache: это обязательная конфигурация, она используется, когда объект необходимо кэшировать, и для этого не определены области кэширования.
  3. cache name=\employee: мы используем элемент кэша для определения региона и его конфигураций. Мы можем определить несколько регионов и их свойства, определяя свойства кэша компонентов модели, мы также можем определить регион со стратегиями кэширования. Кэш свойства легко понять и понять по названию.
  4. Области кэша org.hibernate.cache.internal.StandardQueryCache и org.hibernate.cache.spi.UpdateTimestampsCache определены, поскольку EHCache предупреждал об этом.

Кэш второго уровня Hibernate — модель стратегии кэширования компонентов

Мы используем аннотацию org.hibernate.annotations.Cache для предоставления конфигурации кэширования. org.hibernate.annotations.CacheConcurrencyStrategy используется для определения стратегии кэширования, и мы также можем определить область кэширования для использования в 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;
	}

}

Обратите внимание, что я использую ту же настройку базы данных, что и в примере HQL, вы можете проверить это, чтобы создать таблицы базы данных и загрузить образцы данных.

Служебный класс Hibernate SessionFactory

У нас есть простой служебный класс для настройки спящего режима и получения одноэлементного экземпляра 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 {
            // 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;
    }
}

Наш проект кэширования второго уровня гибернации с использованием Hibernate EHCache готов, давайте напишем простую программу для его тестирования.

Программа тестирования 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"));
		
		//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 предоставляет статистику Hibernate SessionFactory, мы используем ее для вывода количества выборок и количества попаданий, промахов и вводов в кэш второго уровня. Статистика отключена по умолчанию для лучшей производительности, поэтому я включаю ее при запуске программы. Когда мы запускаем вышеуказанную программу, мы получаем много вывода, сгенерированного API-интерфейсами Hibernate и EHCache, но нас интересуют данные, которые мы печатаем. Пример запуска печатает следующие выходные данные.

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

Как видно из вывода, статистика сначала была отключена, но мы включили ее для проверки кэша второго уровня в спящем режиме. Пошаговое объяснение вывода выглядит следующим образом:

  1. Прежде чем мы загрузим какие-либо данные в наше приложение, вся статистика равна 0, как и ожидалось.
  2. Когда мы загружаем сотрудника с id=1 в первый раз, он сначала ищется в кеше первого уровня, а затем в кеше второго уровня. Если он не найден в кеше, выполняется запрос к базе данных, и, следовательно, счетчик выборки становится равным 1. После загрузки объекта он сохраняется как в кеше первого уровня, так и в кеше второго уровня. Таким образом, количество попаданий вторичного уровня остается равным 0, а количество промахов становится равным 1. Обратите внимание, что количество попаданий равно 2, потому что объект Employee также состоит из адреса, поэтому оба объекта сохраняются в кэше второго уровня, а количество увеличивается до 2.
  3. Далее мы снова загружаем сотрудника с id=1, на этот раз он присутствует в кеше первого уровня. Таким образом, вы не видите никаких запросов к базе данных, и вся остальная статистика кэша вторичного уровня также остается неизменной.
  4. Затем мы используем метод evict() для удаления объекта сотрудника из кеша первого уровня, теперь, когда мы пытаемся его загрузить, спящий режим находит его в кеше второго уровня. Вот почему ни один запрос к базе данных не запускается, а количество выборок остается равным 1. Обратите внимание, что количество обращений изменяется от 0 до 2, поскольку объекты Employee и Address считываются из кэша второго уровня. Количество промахов и путов второго уровня остается прежним.
  5. Затем мы загружаем сотрудника с идентификатором = 3, выполняется запрос к базе данных, и количество выборок увеличивается до 2, количество промахов увеличивается с 1 до 2, а количество вложений увеличивается с 2 до 4.
  6. Затем мы пытаемся загрузить сотрудника с id=1 в другом сеансе. Поскольку кэш второго уровня спящего режима является общим для сеансов, он находится в кеше второго уровня, и запрос к базе данных не выполняется. Счетчик выборок, промахов и вводов остается прежним, а количество попаданий увеличивается с 2 до 4.

Итак, понятно, что наш кеш второго уровня Hibernate; Гибернация EHCache; работает нормально. Статистика гибернации помогает найти узкое место в системе и оптимизировать ее, чтобы уменьшить количество выборок и загрузить больше данных из кэша. Это все, что касается примера Hibernate EHCache, я надеюсь, что он поможет вам настроить EHCache в ваших спящих приложениях и повысить производительность за счет спящего кеша второго уровня. Вы можете скачать пример проекта по ссылке ниже и использовать другие статистические данные, чтобы узнать больше.

Скачать проект Hibernate EHCache