一、Hibernate的二级缓存管理策略的一般过程如下:
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象;
2) 把获得的所有数据对象根据ID放入到第二级缓存中;
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
注:Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache(查询缓存)。
一般我们只关心缓存的三个操作:将数据放入缓存、将从缓存中获得数据、将数据从缓存中清除。在二级缓存中,可以通过以下方法执行这三个操作:
将数据放入缓存:save(该方法不适合native方式的主键),update(),saveOrUpdate(),list(),iterator(),get(),load(),以及Query、Criteria都会填充二级缓存(但只适用于查询缓存时);
将数据从缓存中取出:session的iterator(),get(),load()方法都会从缓存中取出数据(iterator()方法肯恩会存在N+1问题);
将数据从缓存中清除:清除二级缓存时,只能对某个指定的对象进行清空,不能一次全部清空。用evict()方法用于清除二级缓存中指定对象数据。
二、什么样的数据适合存放到第二级缓存中?
1 很少被修改的数据;
2 不是很重要的数据,允许出现偶尔并发的数据(重要数据:金融账户数据);
3 不会被并发访问的数据;
4 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。
三、不适合存放到第二级缓存的数据?
1 经常被修改的数据;
2 财务数据,绝对不允许出现并发;
3 与其他应用共享的数据。
四、常用的缓存插件(第三方缓存实现)
Hibernater 的二级缓存是一个插件,下面是几种常用的缓存插件:
EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持;
OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持;
SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存;
JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。
五、 配置二级缓存的主要步骤:
首先,选择需要使用二级缓存的持久化类(有两种方式告诉Hibernate那个实体类需要用二级缓存:一是在hibernate.cfg.Xml文件中配置,另一种方式是在本身的实体关系映射文件中配置),设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤;
其次,选择合适的缓存插件,然后编辑该插件的配置文件。
接下来,使用EhCache配置二级缓存:
配置准备:
1) 把ehcache-1.2.3.jar加入到当前应用的classpath中;
2) 在hibernate.cfg.xml文件中加入EhCache缓存插件的提供类:
<!-- 配置缓存插件 -->
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider
</property>
<!-- 打开二级缓存,二级缓存默认是打开的,所以不配置该节点也是可以的 -->
<property name="cache.use_second_level_cache">true</property>
3) 挎贝ehcache.xml文件到类路径(项目工程的src目录下),这个文件在Hibernate安装目录的etc下。
配置步骤:
Hibernate允许在类和集合的粒度上设置第二级缓存,在映射文件中,< class>和< set>元素都有一个<cache>子元素,这个子元素用来配置二级缓存。
示例:以category(产品类别)和product(产品)的映射为例:
1) 修改要配置缓存的持久化类的对象关系映射文件:
Category.hbm.xml
< ?xml version="1.0" encoding="utf-8"?>
< !DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
< hibernate-mapping>
< class name="org.qiujy.domain.cachedemo.Category" table="categories">
< !--
配置缓存,必须紧跟在class元素后面
对缓存中的Category对象采用读写型的并发访问策略,
注:如果不在实体映射文件中的<class>节点之后配置二级缓存,也可以在
Hibernate.cfg.Xml中配置<class-cache>节点,两者效果一样,两处都进行 配置也不会报错。如下:
<class-cache usage="read- write"class="org.qiujy.domain.cachedemo.Category"/>
-->
< cache usage="read-write"/>
< id name="id" type="java.lang.Long">
< column name="id" />
< generator class="native" />
< /id>
<!-- 配置版本号,必须紧跟在id元素后面 -->
< version name="version" column="version" type="java.lang.Long" />
< property name="name" type="java.lang.String">
< column name="name" length="32" not-null="true"/>
< /property>
< property name="description" type="java.lang.String">
< column name="description" length="255"/>
< /property>
< set name="products" table="products" cascade="all" inverse="true">
< !--
Hibernate只会缓存对象的简单属性的值,要缓存集合属性,必须在集合元素中也加入< cache>子元素,而Hibernate仅仅是把与当前持久对象关联的对象的OID存放到缓存中。如果希望把整个关联的对象的所有数据都存入缓存,则要在相应关联的对象的映射文件中配置< cache>元素
-->
< cache usage="read-write"/>
< key column="categoryId" not-null="true"/>
< on
< /set>
< /class>
< /hibernate-mapping>
Product.hbm.xml
< ?xml version="1.0" encoding="utf-8"?>
< !DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
< hibernate-mapping>
< class name="org.qiujy.domain.cachedemo.Product" table="products">
< cache usage="read-write"/>
< id name="id" type="java.lang.Long">
< column name="id" />
< generator class="native" />
< /id>
< !-- 配置版本号,必须紧跟在id元素后面 该节点不配置也可以,配置的话,会报错 -->
< version name="version" column="version" type="java.lang.Long" />
< property name="name" type="java.lang.String">
< column name="name" length="32" not-null="true"/>
< /property>
< property name="description" type="java.lang.String">
< column name="description" length="255"/>
< /property>
< property name="unitCost" type="java.lang.Double">
< column name="unitCost" />
< /property>
< property name="pubTime" type="java.util.Date">
< column name="pubTime" not-null="true" />
< /property>
< many-to-on
< /class>
< /hibernate-mapping>
2) 编辑ehcache.xml文件:
< ehcache>
< diskStore path="c:\\ehcache\"/>
< defaultCache maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
< !-- 设置Category类的缓存的数据过期策略 -->
< cache name="org.qiujy.domain.cachedemo.Category"
maxElementsInMemory="100"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
< !-- 设置Category类的products集合的缓存的数据过期策略 -->
< cache name="org.qiujy.domain.cachedemo.Category.products"
maxElementsInMemory="500"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
< cache name="org.qiujy.domain.cachedemo.Product"
maxElementsInMemory="500"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
< /ehcache>
配置的元素说明:
元素或属性 描述
<diskStore> 设置缓存数据文件的存放目录
<defaultCache> 设置缓存的默认数据过期策略
<cache> 设定具体的命名缓存的数据过期策略
每个命名缓存代表一个缓存区域,每个缓存区域有各自的数据过期策略。命名缓存机制使得用户能够在每个类以及类的每个集合的粒度上设置数据过期策略。
cache元素的属性
name 设置缓存的名字,它的取值为类的全限定名或类的集合的名字
maxInMemory 设置基于内存的缓存中可存放的对象最大数目
eternal 设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds和timeToLiveSeconds属性;默认值是false
timeToIdleSeconds 设置对象空闲最长时间,超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
timeToLiveSeconds 设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中。
overflowToDisk 设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中.
编写测试类:(该测试类与上面的类无关,可以按照下面的示例写)
/**
* 演示二级缓存
*/
public void secondCache()
{
// 创建两个session对象
Session session1 = sf.openSession();
Session session2 = sf.openSession();
// 让两个session对象同时去查询同一份数据,因为查询时首先在一级缓存中查找是否有对象
// 如果没有则就要去二级缓存中查找,而二级缓存是共享的,每个session对象都可以得到
// 所以就显示一条查询语句,因为二级缓存中有数据,所以不必再发出第二条语句,直接在
// 二级缓存中拿来用即可,这就提高了程序的性能
PetInfo pet1 = (PetInfo)session1.get(PetInfo.class, "ch0001");
System.out.println("******************"+pet1.getPname()+"********************");
PetInfo pet2 = (PetInfo)session2.get(PetInfo.class, "ch0001");
// 关闭session对象
session1.close();
}
小问题:如何在配置了二级缓存后,测试该数据就是从二级缓存中读取的,而不是从一级缓存中读取的?
测试二级缓存时,先将一级缓存中的对象evict掉或者全部clear掉,再从进行查询,同一条数据同时查询两次,如果显示一条查询语句,则说明二级缓存配置成功。