Hibernate 二级缓存和查询缓存


一、Hibernate的二级缓存简介

与Session相对的是,SessionFactory也提供了相应的缓存机制。SessionFactory缓存可以依据功能和目的的不同而划分为内置缓存和外置缓存。


SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的副本,而预定义SQL语句是在 Hibernate初始化阶段根据映射元数据推导出来的。SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义 SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。

SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据 的副本,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的二级缓存。

Hibernate的二级缓存的实现原理与一级缓存是一样的,也是通过以ID为key的Map来实现对对象的缓存。

由于Hibernate的二级缓存是作用在SessionFactory范围内的,因而它比一级缓存的范围更广,可以被所有的Session对象所共享。


二、二级缓存的工作内容

Hibernate的二级缓存同一级缓存一样,也是针对对象ID来进行缓存。所以说,二级缓存的作用范围是针对根据ID获得对象的查询。

二级缓存的工作可以概括为以下几个部分:

  • 在执行各种条件查询时,如果所获得的结果集为实体对象的集合,那么就会把所有的数据对象根据ID放入到二级缓存中。
  • 当Hibernate根据ID访问数据对象的时候,首先会从Session一级缓存中查找,如果查不到并且配置了二级缓存,那么会从二级缓存中查找,如果还查不到,就会查询数据库,把结果按照ID放入到缓存中。
  • 删除、更新、增加数据的时候,同时更新缓存。

三、二级缓存的适用范围

Hibernate的二级缓存作为一个可插入的组件在使用的时候也是可以进行配置的,但并不是所有的对象都适合放在二级缓存中。

在通常情况下会将具有以下特征的数据放入到二级缓存中:

  • 很少被修改的数据。
  • 不是很重要的数据,允许出现偶尔并发的数据。
  • 不会被并发访问的数据。
  • 参考数据。

而对于具有以下特征的数据则不适合放在二级缓存中:

  • 经常被修改的数据。
  • 财务数据,绝对不允许出现并发。
  • 与其他应用共享的数据。

在这里特别要注意的是对放入缓存中的数据不能有第三方的应用对数据进行更改(其中也包括在自己程序中使用其他方式进行数据的修改,例如,JDBC),因为那样Hibernate将不会知道数据已经被修改,也就无法保证缓存中的数据与数据库中数据的一致性。

四、二级缓存组件

在默认情况下,Hibernate会使用EHCache作为二级缓存组件。但是,可以通过设置 hibernate.cache.provider_class属性,指定其他的缓存策略,该缓存策略必须实现org.hibernate.cache.CacheProvider接口。

通过实现org.hibernate.cache.CacheProvider接口可以提供对不同二级缓存组件的支持。

Hibernate内置支持的二级缓存组件如下表所示。

组件

Provider类

类型

集群

查询缓存

Hashtable

org.hibernate.cache.HashtableCacheProvider

内存

不支持

支持

EHCache

org.hibernate.cache.EhCacheProvider

内存,硬盘

最新支持

支持

OSCache

org.hibernate.cache.OSCacheProvider

内存,硬盘

不支持

支持

SwarmCache

org.hibernate.cache.SwarmCacheProvider

集群

支持

不支持

JBoss TreeCache

org.hibernate.cache.TreeCacheProvider

集群

支持

支持

Hibernate已经不再提供对JCS(Java Caching System)组件的支持了。

五、二级缓存的配置

在使用Hibernate的二级缓存时,对于每个需要使用二级缓存的对象都需要进行相应的配置工作。也就是说,只有配置了使用二级缓存的对象才会被放置在二级缓存中。二级缓存是通过<cache>元素来进行配置的。<cache>元素的属性定义说明如下所示:

<cache usage="transactional|read-write|nonstrict-read-write|read-only"
  region="RegionName" 
  include="all|non-lazy">
</cache>

元素的属性说明如下表所示。

序号

属性

含义和作用

必须

默认值

(1)

usage

指定缓存策略,可选的策略包括:transactional,read-write,nonstrict-read-write或read-only

Y

(2)

region

指定二级缓存区域名

N

(3)

include

指定是否缓存延迟加载的对象。all,表示缓存所有对象;non-lazy,表示不缓存延迟加载的对象

N

all

 六、二级缓存的策略

当多个并发的事务同时访问持久化层的缓存中的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。

在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。因此可以设定以下4种类型的并发访问策略,每一种策略对应一种事务隔离级别。

只读缓存(read-only)

如果应用程序需要读取一个持久化类的实例,但是并不打算修改它们,可以使用read-only缓存。这是最简单,也是实用性最好的策略。

对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。

读/写缓存(read-write)

如果应用程序需要更新数据,可能read-write缓存比较合适。如果需要序列化事务隔离级别,那么就不能使用这种缓存策略。

对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。

不严格的读/写缓存(nonstrict-read-write)

如果程序偶尔需要更新数据(也就是说,出现两个事务同时更新同一个条目的现象很不常见),也不需要十分严格的事务隔离,可能适用nonstrict-read-write缓存。

对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。

事务缓存(transactional)

transactional缓存策略提供了对全事务的缓存,仅仅在受管理环境中使用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。

在上面所介绍的隔离级别中,事务型并发访问策略的隔离级别最高,然后依次是读/写型和不严格读写型,只读型的隔离级别最低。事务的隔离级别越高,并发性能就越低。

七、在开发中使用二级缓存

在这一部分中,将细致地介绍如何在Hibernate中使用二级缓存。在这里所使用的二级缓存组件为EHCache。

关于EHCache的详细信息请参考http://ehcache.sourceforge.net上的内容。

7.1、导入Ehcahe和Hibernate整合包

7.2、修改Hibernate的配置文件

在使用Hibernate的二级缓存时,需要在Hibernate的配置文件中指定缓存提供者对象,以便于Hibernate可以通过其实现对数据的缓存处理。

在这里需要设置的参数是hibernate.cache.region.factory_class,在使用EHCache时,需要将其值设置为org.hibernate.cache.ehcache.EhCacheRegionFactory。还要开启二级缓存,具体要增加的配置如下所示:

<!-- Hibernate整合Ehache实现二级缓存 -->
	<!-- 开启Hibernate的二级缓存 -->
	<property name="hibernate.cache.use_second_level_cache">true</property>
	<!-- 引入EhCache的工具 -->
	<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

7.3、增加EHCache配置参数

在默认情况下,EHCache会到classpath所指定的路径中寻找ehcache.xml文件来作为EHCache的配置文件。

在配置文件中,包含了EHCache进行缓存管理时的一些基本的参数。具体的配置方法如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:noNamespaceSchemaLocation="ehcache.xsd">

<diskStore path="java.io.tmpdir" />

<defaultCache
    maxElementsInMemory="10000" 
    eternal="false" 
    timeToIdleSeconds="120" 
    timeToLiveSeconds="120" 
    overflowToDisk="true" 
    diskPersistent="false" 
    diskExpiryThreadIntervalSeconds="120" 
    memoryStoreEvictionPolicy="LRU" />
</ehcache>

 在这里只是使用EHCache所提供的默认配置文件进行了EHCache的基本配置,对于这些参数的详细含义请参考其官方网站(http: //ehcache.sourceforge.net/)中的资料。在实际的开发中,应该依据自己的具体情况来设置这些参数的值。

7.4、编写实体类

Customer实体类:

/**
 * 客户
 * @author http://www.yiidian.com
 *
 */
public class Customer {
	private Integer id;
	private String name;
	private String gender;

	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	
	
}

7.5、在hibernate.cfg.xml给Customer类加上缓存

<class-cache usage="read-only" class="com.yiidian.domain.Customer"/> 

注意配置的位置:

7.6、编写测试代码

/**
 * 演示二级缓存的作用
 * @author http://www.yiidian.com
 *
 */
public class Demo {

	@Test
	public void test1(){
		
		Session session = HibernateUtil.getSession();
		
		//第1次操作
		Customer cust = session.get(Customer.class, 1);
		System.out.println(cust.getName());
		
		//关闭session
		session.close();
		
		//第2次操作
		session = HibernateUtil.getSession();
		cust = session.get(Customer.class, 1);
		System.out.println(cust.getName());
		
		session.close();
		
	}
	
	
}

控制台输出结果:

Hibernate: 
    select
        customer0_.id as id1_0_0_,
        customer0_.name as name2_0_0_,
        customer0_.gender as gender3_0_0_ 
    from
        t_customer customer0_ 
    where
        customer0_.id=?
Tom
Hibernate: 
    select
        customer0_.id as id1_0_0_,
        customer0_.name as name2_0_0_,
        customer0_.gender as gender3_0_0_ 
    from
        t_customer customer0_ 
    where
        customer0_.id=?
Tom

通过上面的结果可以看到,查询Customer信息的时候,第一次查询是从数据库读取,所以执行了SQL语句,但第二次查询Customer对象是从缓存中抓取的,而没有进行数据库的查询操作。

八、查询缓存简介

查询缓存是针对普通属性结果集的缓存,对实体对象的结果集只缓存id。查询缓存的生命周期:当前关联的表发生修改,查询缓存立即失效。

九、查询缓存的配置

在hibernate.cfg.xml文件中启用查询缓存,如:

<property name="hibernate.cache.use_query_cache">true</property>

,因为hibernate中查询缓存默认是关闭的,这和二级缓存不一样。

在程序中必须手动启用查询缓存,如:

query.setCacheable(true);

测试查询缓存:

9.1 没有配置查询缓存之前

// 演示没有查询缓存
	@Test
	public void test2() {

		Session session = HibernateUtil.getSession();

		Query q = session.createQuery("select id,name from Customer");
		q.list();
		session.close();
		
		session = HibernateUtil.getSession();
		q = session.createQuery("select id,name from Customer");
		q.list();
		session.close();

	}

上面代码执行完后,控制台输出:

Hibernate: 
    select
        customer0_.id as col_0_0_,
        customer0_.name as col_1_0_ 
    from
        t_customer customer0_
Hibernate: 
    select
        customer0_.id as col_0_0_,
        customer0_.name as col_1_0_ 
    from
        t_customer customer0_

在两次session的生命周期内,查询两个Customer的相同属性,需要发送两次SQL语句,证明没有缓存做。

9.2、开启查询缓存之后

// 演示查询缓存
	@Test
	public void test2() {

		Session session = HibernateUtil.getSession();

		Query q = session.createQuery("select id,name from Customer");
		q.setCacheable(true);
		q.list();
		session.close();
		
		session = HibernateUtil.getSession();
		q = session.createQuery("select id,name from Customer");
		q.setCacheable(true);
		q.list();
		session.close();

	}

这时控制台输出:

Hibernate: 
    select
        customer0_.id as col_0_0_,
        customer0_.name as col_1_0_ 
    from
        t_customer customer0_

这时只输出一条SQL语句,证明第二次Customer对象是从查询缓存中取出。

 

源码下载:https://pan.baidu.com/s/1dFzuFb7