一、缓存(Cache):计算机领域非常通用的概念。它
介于应用程序和永久性数据存储源
(
如硬盘上的文件或者数据库
)
之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。
缓存的物理介质通常是内存或者硬盘
二、Hibernate中提供了两个级别的缓存
1、第一级别的缓存是 Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的
2、第二级别的缓存是 SessionFactory级别的缓存,它是属于进程范围的缓存
注意:本篇的测试环境使用--HIbernate之HQL所搭建的环境
一级缓存:
缓存范围:缓存只能被当前Session对象访问。缓存的生命周期依赖于Session的生命周期,当Session被关闭后,缓存也就结束生命周期。
@Test
public void testHibernateFirstLevelCache(){
Employee emp=(Employee) session.get(Employee.class, 8);
System.out.println(emp.getName());
Employee emp1=(Employee) session.get(Employee.class, 8);
System.out.println(emp1.getName());
}
控制台信息:
Hibernate:
select
employee0_.ID as ID1_1_0_,
employee0_.NAME as NAME2_1_0_,
employee0_.SALARY as SALARY3_1_0_,
employee0_.EMAIL as EMAIL4_1_0_,
employee0_.DEPT_ID as DEPT_ID5_1_0_
from
EMPLOYEE employee0_
where
employee0_.ID=?
name6
name6
在执行第二个查询的时候并没有发出SQL语句到数据库查询,而是直接从session缓存中读取。
Hibernate一些与一级缓存相关的操作(时间点):
数据放入缓存:
1. save()。当session对象调用save()方法保存一个对象后,该对象会被放入到session的缓存中。
2. get()和load()。当session对象调用get()或load()方法从数据库取出一个对象后,该对象也会被放入到session的缓存中。
3. 使用HQL和QBC等从数据库中查询数据。
二级缓存:
1.SessionFactory级别的缓存,可以跨越Session存在,可以被多个Session所共享
2.SessionFactory的缓存可以分为两类:
–内置缓存: Hibernate 自带的, 不可卸载.通常在Hibernate的初始化阶段,Hibernate 会把映射元数据和预定义的 SQL语句放到SessionFactory的缓存中,映射元数据是映射文件中数据(.hbm.xml文件中的数据)的复制.该内置缓存是只读的.
–外置缓存(二级缓存):一个可配置的缓存插件.在默认情况下,SessionFactory不会启用这个缓存插件.外置缓存中的数据是数据库数据的复制,外置缓存的物理介质可以是内存或硬盘
3.适合放到二级缓存中:
(1)经常被访问
(2)改动不大
(3)数量有限
(4)不是很重要的数据,允许出现偶尔并发的数据。
这样的数据非常适合放到二级缓存中的。
4. 二级缓存的并发访问策略:-两个并发的事务同时访问持久层的缓存的相同数据时,也有可能出现各类并发问题.-二级缓存可以设定以下 4 种类型的并发访问策略,每一种访问策略对应一种事务隔离级别:
–非严格读写(Nonstrict-read-write):不保证缓存与数据库中数据的一致性 . 提供 Read Uncommited 事务隔离级别,对于极少被修改,而且允许脏读的数据,可以采用这种策略– 读写型 (Read-write): 提供 Read Commited 数据隔离级别.对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读–事务型(Transactional):仅在受管理环境下适用. 它提供了 RepeatableRead 事务隔离级别. 对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读–只读型(Read-Only): 提供 Serializable 数据隔离级别,对于从来不会被修改的数据,可以采用这种访问策略
使用Hibernate的二级缓存:
1.选择合适的缓存插件: EHCache(jar包和配置文件), 并编译器配置文件
a、将hibernate\hibernate-release-4.2.5.Final\hibernate-release-4.2.5.Final\lib\optional\ehcache路径下的所有jar包加入到工程的lib目录,并BuildPath!
ehcache-core-2.4.3,jar
hibernate-ehcache-4.2.5.Final.jar
slf4j-api-1.6.1.jar
b、将hibernate\hibernate-release-4.2.5.Final\hibernate-release-4.2.5.Final\project\etc路径下的ehcache.xml配置文件加入到工程的类路径下
2. 修改Hibernate的配置文件:hibernate.cfg.xml ,加入如下配置:
1、 开启二级缓存:
<!-- 启用二级缓存 -->
<property name="cache.use_second_level_cache">true</property>
2、指定使用的二级缓存产品:
<!-- 配置使用的二级缓存产品 -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
3、指定使用二级缓存的持久化类
<!-- 对那个持久化类启用二级缓存 -->
<class-cache usage="read-write" class="com.elgin.hibernate.entity.Employee"/>
配置这一步的时候需要注意:class-cache元素最好放到mapping元素的下面,否则会报xml解析错误:元素类型为 "session-factory" 的内容必须匹配 "(property*,mapping*,(class-cache|collection-cache)*,event*,listener*)"
实际上这个配置也可以配置在持久化类对应的hbm配置文件中。
*******************************二级缓存测试开始**************************
未使用二级缓存的测试:
@Test
public void testHibernateSecondLevelCache(){
Employee emp=(Employee) session.get(Employee.class, 8);
System.out.println(emp.getName());
transcation.commit();
session.close();
session=sessionFactory.openSession();
transcation=session.beginTransaction();
Employee emp1=(Employee) session.get(Employee.class, 8);
System.out.println(emp1.getName());
}
运行之后,控制台显示:
Hibernate:
select
employee0_.ID as ID1_1_0_,
employee0_.NAME as NAME2_1_0_,
employee0_.SALARY as SALARY3_1_0_,
employee0_.EMAIL as EMAIL4_1_0_,
employee0_.DEPT_ID as DEPT_ID5_1_0_
from
EMPLOYEE employee0_
where
employee0_.ID=?
name6
Hibernate:
select
employee0_.ID as ID1_1_0_,
employee0_.NAME as NAME2_1_0_,
employee0_.SALARY as SALARY3_1_0_,
employee0_.EMAIL as EMAIL4_1_0_,
employee0_.DEPT_ID as DEPT_ID5_1_0_
from
EMPLOYEE employee0_
where
employee0_.ID=?
name6
通过控制台结果可以看出:由于第二次查询之前使用了新的session,缓存中无数据,并且未开启二级缓存,所以重新发出了SQL语句进行了查询。
@Test
public void testHibernateSecondLevelCache(){
Employee emp=(Employee) session.get(Employee.class, 8);
System.out.println(emp.getName());
transcation.commit();
session.close();
session=sessionFactory.openSession();
transcation=session.beginTransaction();
Employee emp1=(Employee) session.get(Employee.class, 8);
System.out.println(emp1.getName());
}
控制台打印显示:
Hibernate:
select
employee0_.ID as ID1_1_0_,
employee0_.NAME as NAME2_1_0_,
employee0_.SALARY as SALARY3_1_0_,
employee0_.EMAIL as EMAIL4_1_0_,
employee0_.DEPT_ID as DEPT_ID5_1_0_
from
EMPLOYEE employee0_
where
employee0_.ID=?
name6
name6
通过与未使用的结果对比发现,第二次并没有发出SQL语句,而是直接使用,这就是Hibernate的二级缓存起作用了!
Hibernate集合级别的二级缓存配置:
1. 在上述hibernate配置文件的基础上新增如下配置:
<class-cache usage="read-write" class="com.elgin.hibernate.entity.Department"/>
<collection-cache usage="read-write" collection="com.elgin.hibernate.entity.Department.emps"/>
很明显上面的一句是开启持久化类Department的二级缓存,下面是开启Department下名为emps的集合属性开启二级缓存
注意:开启集合属性二级缓存的同时,一定要为集合中保存的持久化类开启二级缓存(本例中为Employee类,上述配置已开启,直接测试)
@Test
public void testCollectionSecondLevelCache(){
Department dept=(Department) session.get(Department.class, 6);
System.out.println(dept.getName());
System.out.println(dept.getEmps().size());
transcation.commit();
session.close();
session=sessionFactory.openSession();
transcation=session.beginTransaction();
Department dept1=(Department) session.get(Department.class, 6);
System.out.println(dept1.getName());
System.out.println(dept1.getEmps().size());
}
控制台信息:
Hibernate:
select
department0_.ID as ID1_0_0_,
department0_.NAME as NAME2_0_0_
from
DEPARTMENT department0_
where
department0_.ID=?
测试部
Hibernate:
select
emps0_.DEPT_ID as DEPT_ID5_0_1_,
emps0_.ID as ID1_1_1_,
emps0_.ID as ID1_1_0_,
emps0_.NAME as NAME2_1_0_,
emps0_.SALARY as SALARY3_1_0_,
emps0_.EMAIL as EMAIL4_1_0_,
emps0_.DEPT_ID as DEPT_ID5_1_0_
from
EMPLOYEE emps0_
where
emps0_.DEPT_ID=?
2
测试部
2
从上述信息可以看出先查询dept,并且dept中的emps属性使用了延迟加载。在使用新的session查询时,并没有发出SQL语句,而是直接使用,这就是配置的集合缓存的作用!
二级缓存配置文件ehcache.xml的配置
<ehcache>
<!-- Sets the path to the directory where cache .data files are created.
If the path is a Java System Property it is replaced by
its value in the running VM.
The following properties are translated:
user.home - User's home directory
user.dir - User's current working directory
java.io.tmpdir - Default temp file path
指定一个目录,当 EHCache 把数据写到硬盘上时, 将把数据写到这个目录下.
-->
<diskStore path="java.io.tmpdir"/>
<!--Default Cache configuration. These will applied to caches programmatically created through
the CacheManager.
The following attributes are required for defaultCache:
maxInMemory - Sets the maximum number of objects that will be created in memory
eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the element
is never expired.
timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
if the element is not eternal. Idle time is now - last accessed time
timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
if the element is not eternal. TTL is now - creation time
overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
has reached the maxInMemory limit.
设置缓存的默认数据过期策略
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<!--
设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。
如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../>
Hibernate在不同的缓存区域保存不同的类/集合。
对于类而言,区域的名称是类名。如:com.elgin.hibernate.entity.Employee
对于集合而言,区域的名称是类名加属性名。如com.elgin.hibernate.entity.Department.emps
-->
<!--
name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字
maxElementsInMemory:设置基于内存的缓存中可存放的对象最大数目
eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false
timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。
如果此值为0,表示对象可以无限期地处于空闲状态。
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中.
该属性值必须大于或等于 timeToIdleSeconds 属性值
overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
-->
<cache name="com.elgin.hibernate.entity.Employee"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="com.elgin.hibernate.entity.Department.emps"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
</ehcache>
Hibernate二级缓存的查询缓存
1. 默认情况下,设置的二级缓存对HQL查询和QBC查询是无效的, 可以通过如下设置使其生效:
a、在hibernate的配置文件hibernate.cfg.xml中增加如下配置,声明开启查询缓存
<property name="cache.use_query_cache">true</property>
b、在代码中 调用Query或者Criteria的setCacheable(true)方法
2. 查询缓存依赖于二级缓存,如果想要使用,必须先配置Hibernate的二级缓存
测试代码:
@Test
public void testQueryCache(){
String hql="from Employee";
Query query=session.createQuery(hql);
query.setCacheable(true);
List<Employee> emps=query.list();
System.out.println(emps.size());
List<Employee> emps1=query.list();
System.out.println(emps1.size());
}
控制台显示:
Hibernate:
select
employee0_.ID as ID1_1_,
employee0_.NAME as NAME2_1_,
employee0_.SALARY as SALARY3_1_,
employee0_.EMAIL as EMAIL4_1_,
employee0_.DEPT_ID as DEPT_ID5_1_
from
EMPLOYEE employee0_
20
20
可以看出,第二次查询并没有发出SQL语句,说明第一次的结果被缓存了。