Hibernate的EHCache – Hibernate二级缓存
欢迎来到Hibernate二级缓存示例教程。今天我们将介绍Hibernate EHCache,它是最受欢迎的Hibernate二级缓存提供者。
二级缓存的休眠
在大型应用程序中使用Hibernate的主要好处之一是它对缓存的支持,从而减少数据库查询并提高性能。在之前的示例中,我们探讨了Hibernate的一级缓存,而今天我们将介绍使用Hibernate EHCache实现的二级缓存。Hibernate的二级缓存提供者包括EHCache和Infinispan,但EHCache更受欢迎,我们将在示例项目中使用它。然而,在继续我们的项目之前,我们应该了解不同的对象缓存策略。
-
- 只读: 这种缓存策略适用于始终只读且不会更新的持久化对象。它非常适合读取和缓存应用程序配置和其他静态数据,这些数据永远不会被更新。这是最简单、性能最佳的策略,因为不需要检查对象在数据库中是否已更新,所以没有额外负载。
读写: 对于可以由Hibernate应用程序更新的持久化对象而言,这种策略是适用的。然而,如果数据通过后端或其他应用程序进行了更新,Hibernate将无法知道这些变化,并且数据可能会变得陈旧。因此,在使用此策略时,请确保使用Hibernate API来更新数据。
非限制性读写: 如果应用程序只偶尔需要更新数据且不需要严格的事务隔离,非严格读写缓存可能是合适的选择。
事务性: 事务性缓存策略提供对完全事务性缓存提供程序(如JBoss TreeCache)的支持。这种缓存只能在JTA环境中使用,并且您必须指定hibernate.transaction.manager_lookup_class。
Hibernate EHCache is a library that serves as a caching mechanism for Hibernate framework.
由于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.Olivia.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">scdev123</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
<property name="hibernate.connection.username">scdev</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.Olivia.hibernate.model.Employee" />
<mapping class="com.Olivia.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”: 我们使用cache元素来定义区域及其配置。我们可以定义多个区域及其属性,在定义模型bean的缓存属性时,我们还可以定义具有缓存策略的区域。缓存属性的名称清晰易懂。
- EHCache给出了警告,所以我们定义了缓存区域org.hibernate.cache.internal.StandardQueryCache和org.hibernate.cache.spi.UpdateTimestampsCache。
模型Bean缓存策略 – 二级缓存的休止策略
我们使用org.hibernate.annotations.Cache注解来提供缓存配置。org.hibernate.annotations.CacheConcurrencyStrategy用于定义缓存策略,我们还可以定义模型对象使用的缓存区域。
package com.Olivia.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.Olivia.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 实用类
我们有一个简单的实用类来配置Hibernate并获得SessionFactory的单例实例。
package com.Olivia.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第二级缓存项目,让我们编写一个简单的程序来测试它。
Hibernate EHCache的测试程序
package com.Olivia.hibernate.main;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.stat.Statistics;
import com.Olivia.hibernate.model.Employee;
import com.Olivia.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提供统计信息,我们使用它来打印获取次数和二级缓存的命中、丢失和插入次数。统计信息默认禁用以提高性能,这就是为什么我在程序开始时启用它。当我们运行上述程序时,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
正如您从输出中所看到的,首先是统计功能被禁用了,但我们启用了它来检查我们的二级缓存。对输出的逐步解释如下:
-
- 在我们的应用程序加载任何数据之前,所有的统计数据都是0,正如预期的那样。
-
- 当我们第一次加载ID为1的雇员时,首先会在第一级缓存和第二级缓存中查找。如果在缓存中找不到,则执行数据库查询,因此获取计数变为1。一旦对象加载完毕,它将被保存到第一级缓存和第二级缓存中。因此,二级命中计数保持为0,未命中计数变为1。请注意,放置计数为2,这是因为Employee对象还包含Address对象,所以两个对象都保存到了第二级缓存中,计数增加到2。
-
- 接下来,我们再次加载ID为1的雇员,这次它在第一级缓存中存在。所以您看不到任何数据库查询,所有其他二级缓存的统计信息也保持不变。
-
- 接下来,我们使用evict()方法将雇员对象从第一级缓存中移除,现在当我们尝试加载它时,Hibernate在第二级缓存中找到它。这就是为什么没有执行数据库查询,获取计数仍然为1。请注意,命中计数从0增加到2,因为Employee和Address对象都从第二级缓存中读取。第二级未命中和放置计数保持不变。
-
- 接下来,我们加载ID为3的雇员,执行数据库查询,获取计数增加到2,未命中计数从1增加到2,放置计数从2增加到4。
- 接下来,我们尝试在另一个会话中加载ID为1的雇员,由于Hibernate第二级缓存是跨会话共享的,它在第二级缓存中找到并且不执行数据库查询。获取计数、未命中计数和放置计数保持不变,而命中计数从2增加到4。
很显然,我们的Hibernate二级缓存EHCache运行良好。Hibernate统计信息有助于找到系统中的瓶颈并进行优化,以减少提取次数并从缓存中加载更多数据。这就是关于Hibernate EHCache示例的全部内容,我希望它能帮助您在Hibernate应用程序中配置EHCache,并通过Hibernate二级缓存获得更好的性能。您可以从下面的链接下载示例项目并使用其他统计数据来了解更多信息。
下载Hibernate EHCache项目