Hibernate EHCache –Hibernate 二级缓存

时间:2020-02-23 14:41:24  来源:igfitidea点击:

欢迎使用Hibernate二级缓存示例教程。
今天,我们将研究最流行的Hibernate Second Level Cache提供程序Hibernate EHCache。

Hibernate 二级缓存

在大型应用程序中使用Hibernate的主要好处之一是它对缓存的支持,从而减少了数据库查询并提高了性能。
在前面的示例中,我们研究了Hibernate一级缓存,今天我们将使用Hibernate EHCache实现研究Hibernate二级缓存。

Hibernate Second Level缓存提供程序包括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.theitroad.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">hyman123</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
		<property name="hibernate.connection.username">hyman</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.theitroad.hibernate.model.Employee" 
		<mapping class="com.theitroad.hibernate.model.Address" 
	</session-factory>
</hibernate-configuration>

有关Hibernate 二级缓存配置的一些重要要点是:

  • 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:这是一项强制性配置,用于需要缓存Object且没有为此定义缓存区域的情况下使用。

  • 缓存名称="员工":我们使用缓存元素来定义区域及其配置。
    我们可以定义多个区域及其属性,在定义模型bean缓存属性的同时,还可以使用缓存策略定义区域。
    高速缓存属性易于理解,并可以通过名称清除。

  • 定义了缓存区域" org.hibernate.cache.internal.StandardQueryCache"和" org.hibernate.cache.spi.UpdateTimestampsCache",因为EHCache对此发出警告。

Hibernate 二级缓存–模型Bean缓存策略

我们使用" org.hibernate.annotations.Cache"注释来提供缓存配置。
org.hibernate.annotations.CacheConcurrencyStrategy用于定义缓存策略,我们还可以定义用于模型bean的缓存区域。

package com.theitroad.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.theitroad.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.theitroad.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.theitroad.hibernate.main;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.stat.Statistics;

import com.theitroad.hibernate.model.Employee;
import com.theitroad.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=hyman, Zipcode=95129
* 1 *
Fetch Count=1
Second Level Hit Count=0
Second Level Miss Count=1
Second Level Put Count=2
2:: Name=hyman, Zipcode=95129
* 2 *
Fetch Count=1
Second Level Hit Count=0
Second Level Miss Count=1
Second Level Put Count=2
3:: Name=hyman, 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=hyman, 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的员工,这一次它存在于一级缓存中。
    因此,您看不到任何数据库查询,并且所有其他二级缓存的统计信息也保持不变。

  • 接下来,我们使用" evict()"方法从第一级缓存中删除employee对象,现在当我们尝试加载它时,hibernate在第二级缓存中找到它。
    这就是为什么不会触发任何数据库查询并且提取计数保持为1的原因。
    请注意,命中计数从0变为2,因为Employee和Address对象都是从第二级缓存中读取的。
    第二级未命中和看跌计数保持在较早的值。

  • 接下来,我们加载一个id = 3的员工,执行数据库查询,提取计数增加到2,未命中计数从1增加到2,放置数从2增加到4。

  • 接下来,我们尝试在另一个会话中加载id = 1的员工,因为Hibernate 二级缓存是跨会话共享的,因此在二级缓存中可以找到它,并且不执行数据库查询。
    提取计数,未命中计数和看跌计数保持不变,而命中计数从2增加到4。

因此很明显,我们的Hibernate二级缓存;Hibernate EHCache;工作正常。
Hibernate 统计信息有助于发现系统中的瓶颈并对其进行优化,以减少获取次数并从缓存中加载更多数据。