歡迎來到Hibernate二級快取示例教程。今天我們將介紹Hibernate EHCache,這是最流行的Hibernate二級快取提供者。
Hibernate二級快取
在大型應用程式中使用Hibernate的一個主要好處是它對快取的支持,從而減少了數據庫查詢並提高了性能。在之前的示例中,我們研究了Hibernate一級快取,今天我們將使用Hibernate EHCache實現來研究Hibernate二級快取。Hibernate二級快取提供者包括EHCache和Infinispan,但EHCache更受歡迎,我們將在示例項目中使用它。然而,在我們移動到項目之前,我們應該了解不同的對象快取策略。
- 只讀:此快取策略應該用於始終讀取但從不更新的持久對象。它適用於讀取和緩存應用程式配置和其他永遠不會更新的靜態數據。這是最簡單的策略,性能最好,因為不需要檢查對象是否在數據庫中更新。
- 讀寫:對於可以由 Hibernate 應用程式更新的持久性物件來說,這是一個不錯的選擇。但是,如果數據通過後端或其他應用程式進行更新,那麼 Hibernate 將無法得知,數據可能會過時。因此,在使用此策略時,請確保使用 Hibernate API 來更新數據。
- 非受限讀寫:如果應用程式僅偶爾需要更新數據,且不需要嚴格的事務隔離,則非嚴格讀寫緩存可能是合適的選擇。
- 事務性:事務性緩存策略支援完全事務性的緩存提供者,例如 JBoss TreeCache。這樣的緩存只能在 JTA 環境中使用,並且您必須指定 hibernate.transaction.manager_lookup_class。
Hibernate EHCache
由於 EHCache 支援上述所有緩存策略,因此在尋找 Hibernate 中的二級緩存時,它是最佳選擇。我將不會深入探討 EHCache,我的主要重點將是使其在 Hibernate 應用程式中正常運作。在 Eclipse 或您喜歡的 IDE 中創建一個 Maven 專案,最終實現將如下圖所示。 讓我們逐個查看應用程式的每個組件。
使用 Hibernate EHCache Maven 依賴
對於 Hibernate 的二級快取,我們需要在應用程序中添加 ehcache-core 和 hibernate-ehcache 的依賴。EHCache 使用 slf4j 進行日誌記錄,因此我還添加了 slf4j-simple 來進行日誌記錄。我正在使用所有這些 API 的最新版本,有一點機會是 hibernate-ehcache 的 API 與 ehcache-core 的 API 不兼容,在這種情況下,您需要檢查 hibernate-ehcache 的 pom.xml 以找出要使用的正確版本。我們最終的 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 二級快取配置的一些重要注意事項是:
- hibernate.cache.region.factory_class 用於定義二級緩存的工廠類,我正在使用
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”: 我們使用緩存元素來定義區域及其配置。我們可以定義多個區域及其屬性,在定義模型 bean 的緩存屬性時,我們也可以定義具有緩存策略的區域。緩存屬性易於理解,並且以名稱清晰明確。
- 因為 EHCache 發出了警告,所以定義了緩存區域
org.hibernate.cache.internal.StandardQueryCache
和org.hibernate.cache.spi.UpdateTimestampsCache
。
Hibernate 第二級緩存 – 模型 Bean 緩存策略
我們使用 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 Utility Class
我們有一個簡單的實用程序類來配置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 {
// 從hibernate.cfg.xml創建SessionFactory
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"));
// 初始化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);
// 清除一級緩存,以便使用二級緩存
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和EHCache API生成的输出,但我们只关心我们正在打印的数据。示例运行打印以下输出。
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的Employee时,首先在一级缓存中搜索,然后在二级缓存中搜索。如果在缓存中找不到,则执行数据库查询,因此获取次数变为1。一旦对象加载完成,它将保存在一级缓存和二级缓存中。因此,二级命中计数保持为0,未命中计数变为1。请注意,放入次数为2,这是因为Employee对象还包含Address,因此两个对象都保存在二级缓存中,计数增加到2。
- 接下来,我们再次加载id=1的Employee,这次它存在于一级缓存中。因此,您看不到任何数据库查询,所有其他二级缓存统计信息也保持不变。
- 接下來,我們使用
evict()
方法從第一級緩存中刪除員工對象,現在當我們嘗試加載它時,Hibernate會在第二級緩存中找到它。這就是為什麼沒有執行數據庫查詢,而提取計數保持為1的原因。請注意,命中計數從0增加到2,因為員工和地址對象都從第二級緩存中讀取。第二級未命中和放置計數保持在先前的值。 - 接下來,我們加載id = 3的員工,執行數據庫查詢,提取計數增加到2,未命中計數從1增加到2,放置計數從2增加到4。
- 接下來,我們嘗試在另一個會話中加載id = 1的員工,由於Hibernate第二級緩存在會話之間是共享的,它被發現在第二級緩存中,因此不執行數據庫查詢。提取計數、未命中計數和放置計數保持不變,而命中計數從2增加到4。
所以很明顯,我們的Hibernate第二級緩存;Hibernate EHCache;運作良好。Hibernate統計信息有助於找出系統中的瓶頸並對其進行優化,以減少提取計數並從緩存中加載更多數據。關於Hibernate EHCache示例就是這些了,我希望它能幫助您在Hibernate應用程序中配置EHCache並通過Hibernate第二級緩存獲得更好的性能。您可以從下面的鏈接下載示例項目並使用其他統計數據進一步了解。
Source:
https://www.digitalocean.com/community/tutorials/hibernate-ehcache-hibernate-second-level-cache