在《Hibernate一级缓存——Session》中介绍了Session级别的一级缓存,例如,当如下的代码执行时,由于一级缓存的作用,只会发送一条select语句:
@Test
public void testCache(){
Employee employee1 = (Employee) session.get(Employee.class, 1);
System.out.println(employee1);
Employee employee2 = (Employee) session.get(Employee.class, 1);
System.out.println(employee2);
}
现在,我们在两次加载代码之间,先关闭当前的session和事务,再重新开启一个session和事务,那么不难理解,由于开启了新的session,所以会打印两条select语句:
@Test
public void testCache(){
Employee employee1 = (Employee) session.get(Employee.class, 1);
System.out.println(employee1);
//提交事务,关session
transaction.commit();
session.close();
//开启一个新的session和事务
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Employee employee2 = (Employee) session.get(Employee.class, 1);
System.out.println(employee2);
}
那么,有没有办法使就算session关闭,也能缓存employee对象的办法呢?这就是我们现在要介绍的,SessionFactory级别的,Hibernate二级缓存。
缓存(Cache )是计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝,应用程序在运行时直接读写缓存中的数据,只在某些特定时刻按照缓存中的数据来同步更新数据存储源。缓存的物理介质通常是内存。
Hibernate中提供了两个级别的缓存:
1.第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存,即缓存只能被当前事务访问,每个事务都有独自的缓存。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。这一级别的缓存是由hibernate管理的,一般情况下无须进行干预。
2.第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围的缓存。缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。
SessionFactory 的缓存可以分为两类:
内置缓存: Hibernate自带的,不可卸载。通常在Hibernate的初始化阶段,Hibernate会把映射元数据和预定义的SQL语句放到SessionFactory的缓存中,映射元数据是映射文件中数据的复制,而预定义SQL语句是Hibernate根据映射元数据推到出来的。该内置缓存是只读的。
外置缓存(二级缓存):一个可配置的缓存插件。在默认情况下,SessionFactory不会启用这个缓存插件。外置缓存中的数据是数据库中数据的复制,外置缓存的物理介质可以是内存,也可以是硬盘。
缓存的物理介质通常是内存,而永久性数据存储源的物理介质通常是硬盘或磁盘,应用程序读写内在的速度显然比读写硬盘的速度快,如果缓存中存放的数据量非常大,也会用硬盘作为缓存的物理介质。缓存的实现不仅需要作为物理介质的硬件,同时还需要用于管理缓存的并发访问和过期等策略的软件。因此,缓存是通过软件和硬件共同实现的。
适合放入二级缓存中的数据:
1.很少被修改;
2.不是很重要的数据,允许出现偶尔的并发问题。
不适合放入二级缓存中的数据:
1.经常被修改;
2.财务数据,绝对不允许出现并发问题;
3.与其他应用程序共享的数据。
二级缓存的并发访问策略:
两个并发的事务同时访问持久层的缓存的相同数据时,也有可能出现各类并发问题。
二级缓存可以设定以下4种类型的并发访问策略,每一种访问策略对应一种事务隔离级别。
非严格读写(Nonstrict-read-write):不保证缓存与数据库种数据的一致性。提供read uncommitted事务隔离级别。对于极少被修改,而且允许脏读的数据,可以采用这种策略。
读写型(Read-write):提供read committed事务隔离级别。对于经常读但是很少被修改的数据,可以采用这种隔离类型,它可以防止脏读。(通常选用的策略)
事务型(Transactional):仅在受管理环境下适用。它提供了repeatable read的事务隔离级别。对于经常读,但是很少被修改的数据,可以采用这种隔离类型,它可以防止脏读和不可重复读。
只读型(Read-Only):提供serializable事务隔离级别,对于从来不会修改的数据,可以采用这种访问策略,可以避免脏读,不可重复读和幻读。
管理Hibernate的二级缓存:
Hibernate的二级缓存是进程或者集群范围内的缓存。
二级缓存是可配置的插件,Hibernate允许选用以下类型的缓存插件:
EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。
OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。
SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。 JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。
一、加入二级缓存的jar包及配置文件
1.加入jar包
2.添加配置文件ehcache.xml到src目录下
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<cache name="sampleCache1"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="sampleCache2"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/> -->
ehcache>
二、配置hibernate.cfg.xml
1.配置启用hibernate的二级缓存
<property name="cache.use_second_level_cache">trueproperty>
2.配置hibernate二级缓存使用的产品
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactoryproperty>
3.配置对哪些类(或属性)使用 hibernate 的二级缓存
第一种情况,类级别的二级缓存:(配置对Employee类使用二级缓存)
<class-cache usage="read-write" class="com.atguigu.hibernate.entities.Employee"/>
第二种情况,集合级别的二级缓存:(配置对Department类中的Employee集合属性emps使用二级缓存)
<collection-cache usage="read-write" collection="com.atguigu.hibernate.entities.Department.emps"/>
注意,当配置对集合属性使用二级缓存时,还需要对集合所属的类,集合中元素的类型使用二级缓存,例如,对于上述集合属性,则还需配置对Department类和Employee类使用二级缓存:(如果不对Employee类配置二级缓存,则会多出n条SQL语句,得不偿失。因为这种情况下缓存的是一个一个的employee的id,当要使用到employee对象时,需要再根据id一条一条地去数据库查询记录)
<class-cache usage="read-write" class="com.atguigu.hibernate.entities.Department"/>
<class-cache usage="read-write" class="com.atguigu.hibernate.entities.Employee"/>
另外,配置对哪些类(或属性)使用二级缓存,还可以在映射文件中配置,例如:
类级别(在class节点下):
<cache usage="read-write"/>
集合级别:(在department映射文件的set节点下)
<cache usage="read-write"/>
现在,测试上面的testCache方法,只会打印一条select语句:
对于集合的测试也是一样,只会打印两条select语句,一条用于查询department,一条用于查询employee:
@Test
public void testCollectionSecondLevelCache(){
Department dept = (Department) session.get(Department.class, 1);
System.out.println(dept.getName());
System.out.println(dept.getEmps().size());
transaction.commit();
session.close();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Department dept2 = (Department) session.get(Department.class, 1);
System.out.println(dept2.getName());
System.out.println(dept2.getEmps().size());
}
下面使用一个修改过的二级缓存配置文件介绍其中各个属性的作用:
<ehcache>
<diskStore path="d:\\tempDirectory"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<cache name="com.atguigu.hibernate.entities.Employee"
maxElementsInMemory="1"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="com.atguigu.hibernate.entities.Department.emps"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
ehcache>
对于经常使用的查询语句,如果启用了查询缓存,当第一次执行查询语句时,Hibernate会把查询结果存放在查询缓存中,以后再次执行该查询语句时,只需从缓存中获得查询结果,从而提高查询性能。
默认情况下,Hibernate设置的缓存对HQL和QBC查询是无效的,但可以通过以下步骤使其有效:
1.配置二级缓存,因为查询缓存依赖于二级缓存.
2.在hibernate配置文件中声明开启查询缓存。
<property name="cache.use_query_cache">trueproperty>
3.调用Query或者Criteria的setCachable(true)。
例如,在没有配置查询缓存的情况下,下面的代码会打印两条select语句:
@Test
public void testQueryCache(){
Query query = session.createQuery("FROM Employee");
List emps = query.list();
System.out.println(emps.size());
emps = query.list();
System.out.println(emps.size());
}
进行了相关配置之后,并且在方法中设置query.setCacheable(true);则只会打印一条select语句:
@Test
public void testQueryCache(){
Query query = session.createQuery("FROM Employee");
query.setCacheable(true);
List emps = query.list();
System.out.println(emps.size());
emps = query.list();
System.out.println(emps.size());
}
查询缓存适用于如下场合:
1.应用程序运行时经常使用查询语句
2.很少对与查询语句检索到的数据进行插入,删除或更新操作
时间戳缓存区域存放了对于查询结果相关的表进行插入,更新或者删除操作的时间戳。Hibernate通过时间戳缓存区域来判断被缓存的查询结果是否过期,其运行过程如下:
T1时刻执行查询操作,把结果存放在QueryCache区域,记录该区域的时间戳为T1;
T2时刻(可能在T1之前,也可能在T1之后)对查询结果相关的表进行更新操作,Hibernate把T2时刻存放在UpdateTimestampCache区域。
T3时刻(在T1,T2之后)执行查询结果前,先比较QueryCache区域的时间戳和UpdateTimestampCache区域的时间戳。若T2>T1,则丢弃原先存放在QueryCache区域的查询结果,重新到数据库中查询数据并放入QueryCache区域;若T2
@Test
public void testUpdateTimeStampCache(){
Query query = session.createQuery("FROM Employee");
query.setCacheable(true);
List emps = query.list();
System.out.println(emps.size());
Employee employee = (Employee) session.get(Employee.class, 1);
employee.setSalary(30000);
emps = query.list();
System.out.println(emps.size());
}
在第二次查询的时候,会重新用select去数据库中查找最新的记录。
Query的list方法返回实体类对应表的所有字段,而Query的iterate方法仅返回数据包的ID字段。当使用了iterate方法,然后遍历访问结果集时,先到Session缓存及二级缓存中查看是否存在特定OID的对象,如果存在,直接返回,否则就通过相应的SQL SELECT语句到数据库中查找特定的记录。
在大多数情况下,应该考虑使用list方法执行查询操作,iterate方法仅在下述情况下可以稍微提高查询性能:
1.要查询的数据表中包含大量字段;
2.启用了二级缓存,且二级缓存中可能已经包含了待查询的对象。