Hibernate EHCache — Кэш второго уровня Hibernate
Добро пожаловать в учебник по примерам кэша второго уровня Hibernate. Сегодня мы рассмотрим Hibernate EHCache, который является самым популярным поставщиком кэша Hibernate второго уровня.
Спящий режим кэша второго уровня
- Только чтение: эту стратегию кэширования следует использовать для постоянных объектов, которые всегда будут читаться, но никогда не обновляться. Это удобно для чтения и кэширования конфигурации приложений и других статических данных, которые никогда не обновляются. Это самая простая стратегия с наилучшей производительностью, поскольку нет перегрузки для проверки того, обновлен ли объект в базе данных или нет.
- Чтение-запись. Подходит для постоянных объектов, которые могут быть обновлены приложением, находящимся в спящем режиме. Однако, если данные обновляются либо через серверную часть, либо через другие приложения, то спящий режим никак не узнает об этом, и данные могут быть устаревшими. Поэтому при использовании этой стратегии убедитесь, что вы используете Hibernate API для обновления данных.
- Неограниченное чтение-запись. Если приложению требуется обновлять данные лишь изредка и строгая изоляция транзакций не требуется, может подойти нестрогий кэш-чтение-запись.
- Транзакционный. Стратегия транзакционного кэша обеспечивает поддержку поставщиков полностью транзакционного кэша, таких как 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>
Вот некоторые важные моменты, касающиеся конфигурации кэша второго уровня гибернации:
- 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
. - hibernate.cache.use_second_level_cache используется для включения кэша второго уровня.
- hibernate.cache.use_query_cache используется для включения кэширования запросов, без него результаты запросов HQL не будут кэшироваться.
- 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 предоставляет множество опций, я не буду вдаваться в подробности, но некоторые из важных конфигураций выше:
- diskStore: EHCache сохраняет данные в памяти, но когда она начинает переполняться, она начинает записывать данные в файловую систему. Мы используем это свойство, чтобы определить место, куда EHCache будет записывать переполненные данные.
- DefaultCache: это обязательная конфигурация, она используется, когда объект необходимо кэшировать, и для этого не определены области кэширования.
- cache name=\employee: мы используем элемент кэша для определения региона и его конфигураций. Мы можем определить несколько регионов и их свойства, определяя свойства кэша компонентов модели, мы также можем определить регион со стратегиями кэширования. Кэш свойства легко понять и понять по названию.
- Области кэша
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
Как видно из вывода, статистика сначала была отключена, но мы включили ее для проверки кэша второго уровня в спящем режиме. Пошаговое объяснение вывода выглядит следующим образом:
- Прежде чем мы загрузим какие-либо данные в наше приложение, вся статистика равна 0, как и ожидалось.
- Когда мы загружаем сотрудника с id=1 в первый раз, он сначала ищется в кеше первого уровня, а затем в кеше второго уровня. Если он не найден в кеше, выполняется запрос к базе данных, и, следовательно, счетчик выборки становится равным 1. После загрузки объекта он сохраняется как в кеше первого уровня, так и в кеше второго уровня. Таким образом, количество попаданий вторичного уровня остается равным 0, а количество промахов становится равным 1. Обратите внимание, что количество попаданий равно 2, потому что объект Employee также состоит из адреса, поэтому оба объекта сохраняются в кэше второго уровня, а количество увеличивается до 2.
- Далее мы снова загружаем сотрудника с id=1, на этот раз он присутствует в кеше первого уровня. Таким образом, вы не видите никаких запросов к базе данных, и вся остальная статистика кэша вторичного уровня также остается неизменной.
- Затем мы используем метод
evict()
для удаления объекта сотрудника из кеша первого уровня, теперь, когда мы пытаемся его загрузить, спящий режим находит его в кеше второго уровня. Вот почему ни один запрос к базе данных не запускается, а количество выборок остается равным 1. Обратите внимание, что количество обращений изменяется от 0 до 2, поскольку объекты Employee и Address считываются из кэша второго уровня. Количество промахов и путов второго уровня остается прежним. - Затем мы загружаем сотрудника с идентификатором = 3, выполняется запрос к базе данных, и количество выборок увеличивается до 2, количество промахов увеличивается с 1 до 2, а количество вложений увеличивается с 2 до 4.
- Затем мы пытаемся загрузить сотрудника с id=1 в другом сеансе. Поскольку кэш второго уровня спящего режима является общим для сеансов, он находится в кеше второго уровня, и запрос к базе данных не выполняется. Счетчик выборок, промахов и вводов остается прежним, а количество попаданий увеличивается с 2 до 4.
Итак, понятно, что наш кеш второго уровня Hibernate; Гибернация EHCache; работает нормально. Статистика гибернации помогает найти узкое место в системе и оптимизировать ее, чтобы уменьшить количество выборок и загрузить больше данных из кэша. Это все, что касается примера Hibernate EHCache, я надеюсь, что он поможет вам настроить EHCache в ваших спящих приложениях и повысить производительность за счет спящего кеша второго уровня. Вы можете скачать пример проекта по ссылке ниже и использовать другие статистические данные, чтобы узнать больше.
Скачать проект Hibernate EHCache