Добро пожаловать в Руководство по примеру второго уровня кеша Hibernate. Сегодня мы рассмотрим Hibernate EHCache, который является самым популярным поставщиком кеша второго уровня Hibernate.
Кеш второго уровня Hibernate
Одним из основных преимуществ использования Hibernate в больших приложениях является его поддержка кеша, что позволяет снизить количество запросов к базе данных и повысить производительность. В предыдущем примере мы рассмотрели Кеш первого уровня Hibernate, а сегодня рассмотрим второй уровень кеша Hibernate с использованием реализации Hibernate EHCache. Поставщики кеша второго уровня Hibernate включают EHCache и Infinispan, но EHCache более популярен, и мы будем использовать его в нашем проекте. Однако перед тем, как перейти к нашему проекту, мы должны знать различные стратегии кеширования объекта.
- Только для чтения: Эта стратегия кеширования должна использоваться для постоянных объектов, которые всегда будут только читаться, но никогда не обновляться. Это подходит для чтения и кеширования конфигурации приложения и других статических данных, которые никогда не обновляются. Это самая простая стратегия с лучшей производительностью, потому что нет нагрузки на проверку обновления объекта в базе данных или нет.
- Чтение Запись: Это хорошо для постоянных объектов, которые могут быть обновлены приложением Hibernate. Однако, если данные обновляются либо через бэкэнд, либо через другие приложения, то Hibernate не узнает об этом, и данные могут оказаться устаревшими. Поэтому при использовании этой стратегии убедитесь, что вы используете API Hibernate для обновления данных.
- Неограниченное Чтение Запись: Если приложению время от времени требуется обновлять данные и строгая изоляция транзакций не требуется, то подходящим может быть кеш с нестрогим чтением и записью.
- Транзакционный: Стратегия транзакционного кеша обеспечивает поддержку полностью транзакционных поставщиков кеша, таких как JBoss TreeCache. Такой кеш можно использовать только в среде JTA, и вы должны указать hibernate.transaction.manager_lookup_class.
Hibernate EHCache
Поскольку EHCache поддерживает все вышеперечисленные стратегии кеширования, это лучший выбор, когда речь идет о кеше второго уровня в Hibernate. Я не буду вдаваться в подробности EHCache, моя основная задача – заставить его работать в приложении Hibernate. Создайте проект Maven в Eclipse или вашей любимой среде разработки, окончательная реализация будет выглядеть примерно так, как на изображении ниже. Давайте рассмотрим каждый компонент приложения по очереди.
Зависимости Maven для Hibernate EHCache
Для вторичного уровня кеша Hibernate нам потребуется добавить зависимости 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 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:
- hibernate.cache.region.factory_class используется для определения класса Factory для кэширования второго уровня. Я использую
org.hibernate.cache.ehcache.EhCacheRegionFactory
для этого. Если вы хотите, чтобы класс Factory был синглтоном, вы должны использовать класс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: Это обязательная конфигурация, используется, когда объект должен быть кэширован, и для этого нет определенных областей кэширования.
- имя кэша=“сотрудник”: Мы используем элемент кэша для определения области и ее конфигураций. Мы можем определить несколько областей и их свойства, определяя свойства кэша модельных бинов, мы также можем определить область с кэширующими стратегиями. Свойства кэша легко понять и понятны по имени.
- Области кэша
org.hibernate.cache.internal.StandardQueryCache
иorg.hibernate.cache.spi.UpdateTimestampsCache
определены, потому что EHCache давала предупреждение об этом.
Кэш второго уровня Hibernate – стратегия кэширования модельных бинов
Мы используем аннотацию org.hibernate.annotations.Cache
для предоставления конфигурации кэширования. org.hibernate.annotations.CacheConcurrencyStrategy
используется для определения стратегии кэширования, и мы также можем определить область кэша для использования модельными бинами.
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 в примере HQL. Вам, возможно, стоит проверить это, чтобы создать таблицы базы данных и загрузить тестовые данные.
Класс утилиты Hibernate SessionFactory
У нас есть простой утилитарный класс для конфигурации Hibernate и получения единственного экземпляра 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 {
// Создание SessionFactory из 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 с использованием 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"));
// Инициализация сессий
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);
// Очистка кеша первого уровня, чтобы использовать вторичный уровень кеша
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);
// Освобождение ресурсов
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, мы используем ее для вывода количества запросов и попаданий, промахов и добавлений в кеш второго уровня Hibernate. По умолчанию статистика отключена для повышения производительности, поэтому я включаю ее в начале программы. Когда мы запускаем вышеуказанную программу, мы получаем много вывода, сгенерированного Hibernate и EHCache APIs, но нас интересуют только данные, которые мы выводим. Пример вывода печатается следующим образом.
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
Как видно из вывода, статистика сначала была отключена, но мы включили ее для проверки нашего кеша второго уровня Hibernate. Шаг за шагом объяснение вывода следующее.
- Перед тем, как мы загрузим какие-либо данные в нашем приложении, все статистики равны 0, как и ожидалось.
- Когда мы загружаем сотрудника с id=1 в первый раз, он сначала ищется в кеше первого уровня, а затем в кеше второго уровня. Если его нет в кеше, выполняется запрос к базе данных, и, следовательно, счетчик запросов увеличивается на 1. После того как объект загружен, он сохраняется как в кеше первого, так и в кеше второго уровня. Таким образом, счетчик попаданий вторичного уровня остается равным 0, а счетчик промахов становится равным 1. Обратите внимание, что счетчик добавлений равен 2, потому что объект Employee состоит также из объекта Address, поэтому оба объекта сохраняются в кеше второго уровня, и счетчик увеличивается до 2.
- Затем мы снова загружаем сотрудника с id=1, на этот раз он присутствует в кеше первого уровня. Поэтому вы не видите запроса к базе данных, и все остальные статистики кеша второго уровня остаются такими же.
- Next, мы используем метод
evict()
для удаления объекта сотрудника из кеша первого уровня, и когда мы пытаемся загрузить его снова, Hibernate находит его в кеше второго уровня. Поэтому нет запроса к базе данных, и количество извлечений остается 1. Обратите внимание, что количество попаданий увеличивается с 0 до 2, потому что объекты Employee и Address считываются из кеша второго уровня. Количество промахов и обновлений в кеше второго уровня остается на прежнем уровне. -
Затем мы загружаем сотрудника с id=3, выполняется запрос к базе данных, и количество извлечений увеличивается до 2, количество промахов увеличивается с 1 до 2, а количество обновлений увеличивается с 2 до 4.
-
Далее мы пытаемся загрузить сотрудника с id=1 в другой сессии. Поскольку кеш второго уровня Hibernate общий для сессий, он находится в кеше второго уровня, и запрос к базе данных не выполняется. Количество извлечений, промахов и обновлений остается неизменным, в то время как количество попаданий увеличивается с 2 до 4.
Таким образом, ясно, что наш кеш второго уровня Hibernate, Hibernate EHCache, работает исправно. Статистика Hibernate полезна для выявления узких мест в системе и их оптимизации для уменьшения количества извлечений и загрузки большего объема данных из кеша. Это все для Примера Hibernate EHCache. Надеюсь, это поможет вам настроить EHCache в ваших приложениях Hibernate и добиться лучшей производительности с помощью кеша второго уровня Hibernate. Вы можете скачать образец проекта по следующей ссылке и использовать другие данные статистики для более глубокого изучения.
Source:
https://www.digitalocean.com/community/tutorials/hibernate-ehcache-hibernate-second-level-cache