第6章 二级缓存
- Hibernate缓存
Hibernate维护了两个级别的缓存,一个是线程级别的一级缓存,一个是进程级别的二级缓存。其中一级缓存是由Session对象维护的,二级缓存是由SessionFactory维护的。
- Web应用中的进程和线程
在Web应用中Servlet容器也就是服务器的运行对应一个大的进程,而具体每一个请求的处理则是由线程执行的。所以线程级别的一级缓存只能在当前请求处理过程中可用,线程结束就释放了,存在时间很短;而二级缓存工作在进程级别所以只要服务器还在运行就一直有效。
- Hibernate二级缓存概述
- SessionFactory的缓存可以分为两类
①内置缓存:Hibernate自带的,不可卸载。通常在Hibernate的初始化阶段,Hibernate会把映射元数据和预定义的SQL语句放到 SessionFactory的缓存中。映射元数据是映射文件中数据(.hbm.xml文件中的数据)的复制。该内置缓存是只读的。
②外置缓存(二级缓存):由可配置的缓存插件维护。在默认情况下,SessionFactory不会启用这个缓存插件。外置缓存中的数据是数据库数据的复制,外置缓存的物理介质可以是内存或硬盘。
-
- 数据要求
- 适合存入二级缓存的数据
- 很少被修改,经常被查询
- 不重要,允许出现偶尔的并发问题
- 不适合存入二级缓存的数据
- 经常被修改的数据:会由于更新不及时导致二级缓存中的数据总是错误的
- 财务数据,不允许任何的并发问题
- 与其他应用程序共享的数据
-
- Hibernate二级缓存架构
-
- Hibernate二级缓存的并发访问策略
二级缓存可以设定以下4种类型的并发访问策略,每一种访问策略对应一种事务隔离级别
①非严格读写(Nonstrict-read-write):不保证缓存与数据库中数据的一致性。提供Read Uncommited事务隔离级别,对于极少被修改,而且允许脏读的数据,可以采用这种策略
②读写型(Read-write):提供Read Commited数据隔离级别。对于经常读但是很少被修改且不允许脏读的数据,可以采用这种隔离类型。
③事务型(Transactional):仅在受管理环境下适用。它提供了Repeatable Read事务隔离级别,可以防止脏读和不可重复读。
④只读型(Read-Only):提供Serializable数据隔离级别,对于从来不会被修改的数据,可以采用这种访问策略
并发访问策略 | 隔离级别 |
Nonstrict-read-write | Read Uncommited |
Read-write | Read Commited |
Transactional | Repeatable Read |
Read-Only | Serializable |
- 管理Hibernate的二级缓存
①Hibernate的二级缓存是进程或集群范围内的缓存
②二级缓存是由可配置的的插件维护的。Hibernate允许选用以下类型的缓存插件:
[1]EHCache:可作为进程范围内的缓存,存放数据的物理介质可以使内存或硬盘,对Hibernate的查询缓存提供了支持
[2]OpenSymphony OSCache:可作为进程范围内的缓存,存放数据的物理介质可以使用内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持
[3]SwarmCache:可作为集群范围内的缓存,但不支持Hibernate的查询缓存
[4]JBossCache:可作为集群范围内的缓存,支持Hibernate的查询缓存
③4种缓存插件支持的并发访问策略(x 代表支持, 空白代表不支持)
二级缓存插件 | Nonstrict-read-write | Read-write | Transactional | Read-Only |
EHCache | √ | √ |
| √ |
OpenSymphony OSCache | √ | √ |
| √ |
SwarmCache | √ |
|
| √ |
JBossCache |
|
| √ | √ |
- 配置Hibernate二级缓存
①导入EHCache插件的JAR包
hibernate-release-4.2.4.Final\lib\optional\ehcache目录下的所有JAR包
②加入配置文件
hibernate-release-4.2.4.Final\project\etc\ehcache.xml
③在hibernate.cfg.xml配置文件中启用二级缓存
<!-- 启用二级缓存 --> <property name="cache.use_second_level_cache">true</property>
<!-- 配置使用的二级缓存产品 --> <!-- 参考 hibernate-release-4.2.4.Final\project\etc\hibernate.properties文件看到 #hibernate.cache.region.factory_class org.hibernate.cache.internal.EhCacheRegionFactory 但这个值不对,应该是:org.hibernate.cache.ehcache.EhCacheRegionFactory --> <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<mapping resource="com/atguigu/mapping/bean/Department.hbm.xml"/> <mapping resource="com/atguigu/mapping/bean/Employee.hbm.xml"/>
<!-- 配置使用二级缓存的类 --> <class-cache usage="read-write" class="com.atguigu.mapping.bean.Employee"/> |
④也可以在hbm文件中配置
<class name="Employee" table="EMPS">
<!-- 在hbm映射文件中配置二级缓存策略 --> <cache usage="read-write"/> |
- 集合级别的二级缓存
①类级别的二级缓存对集合无效
@Test public void test03() { //尽管配置了类级别的二级缓存,但对于集合而言仍然 不起作用 Department department = (Department) session.get(Department. class, 1); System.out.println(department.getDeptName()); System.out.println(department.getEmpSet().size());
transaction.commit(); session.close();
session = factory.openSession(); transaction = session.beginTransaction();
depart ment = (Department) session.get(Department. class, 1); System. out.println(department.getDeptName()); System. out.println(department.getEmpSet().size()); } |
②配置集合对象使用二级缓存
<!-- 配置集合属性所在的类使用二级缓存 --> <class-cache usage="read-write" class="com.atguigu.mapping.bean.Department"/>
<!-- 配置集合对象使用二级缓存 --> <collection-cache usage="read-write" collection="com.atguigu.mapping.bean.Department.empSet"/> |
③注意
如果配置集合对象的二级缓存,但没有配置集合元素的二级缓存,则对集合对象中保存的就仅仅是元素的OID。在需要使用集合对象时,Hibernate会根据每一个OID再发送SQL语句查询具体的元素对象,导致额外多出很多SQL语句。
④也可以在hbm文件中配置,集合对象在set标签内配置。
<set name="empSet" table="EMPS" inverse="true" lazy="true"> <!-- 在hbm 文件中配置集合对象使用二级缓存 --> <cache usage="read-write"/> <key> <column name="dept_id_fk"/> </key> <one-to-many class="Employee"/> </set> |
- EHCache自身配置文件
<!-- 指定一个目录:当 EHCache 把数据写到硬盘上时, 将把数据写到这个目录下. --> <diskStore path="d:\\tempDirectory"/>
<!-- 设置缓存数据默认的过期策略 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" />
<!-- 设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域 缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。 如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../> Hibernate 在不同的缓存区域保存不同的类/集合。 对于类而言,区域的名称是类名。如:com.atguigu.domain.Customer 对于集合而言,区域的名称是类名加属性名。如com.atguigu.domain.Customer.orders --> <!-- name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字 maxElementsInMemory:设置基于内存的缓存中可存放的对象最大数目
eternal:设置对象是否为永久的,true表示永不过期 此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性;默认值是false
timeToIdleSeconds:设置对象空闲最长时间,以秒为单位,超过这个时间,对象过期。 当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。 如果此值为0,表示对象可以无限期地存在于缓存中。该属性值必须大于或等于timeToIdleSeconds属性值
overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中 --> <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" /> |
- 查询缓存
①默认情况下执行HQL查询语句时,二级缓存不起作用
@Test public void testCache01 () { List<Employee> list = session.createQuery("From Employee e").list(); System. out.println(list.size()); list = session.createQuery("From Employee e").list(); System. out.println(list.size()); } |
②需要开启查询缓存
@Test public void testCache02() { Query query = session.createQuery("From Employee e");
//开启查询缓存 query.setCacheable( true); List<Employee> list = query.list(); System.out.println(list.size());
list = query.list(); System.out.println(list.size()); } |
<!-- 配置启用查询缓存 --> <property name="cache.use_query_cache">true</property> |
- 更新时间戳缓存
时间戳缓存区域存放了对于查询结果相关的表进行插入、更新或删除操作的时间戳。Hibernate通过时间戳缓存区域来判断被缓存的查询结果是否过期。其运行过程如下:
①T1时刻执行查询操作,把查询结果存放在QueryCache区域,记录该区域的时间戳为T1
②T2时刻对查询结果相关的表进行更新操作,Hibernate把T2时刻存放在UpdateTimestampCache区域
③T3时刻执行查询结果前,先比较QueryCache区域的时间戳和UpdateTimestampCache区域的时间戳,若T2>T1,那么就丢弃原先存放在QueryCache区域的查询结果,重新到数据库中查询数据,再把结果存放到QueryCache区域;若T2<T1,直接从QueryCache中获得查询结果
- Query接口的iterate()方法
①和list()方法很相似
②list()方法执行的SQL语句包含实体类对应的数据表的所有字段
③iterate()方法执行的SQL语句中仅包含OID字段
④当遍历访问iterate()方法的结果集时
[1]得到的是集合中的每个OID的值
[2]根据OID的值到Session一级缓存和二级缓存中查询,看是否存在OID对应的实体类对象
[3]如果存在则直接返回该对象
[4]如果不存在则根据OID发送SQL语句查询该对象
⑤大多数场合下,应考虑使用list()方法
⑥iterate()方法仅在特定场合下能够稍微提高一点查询效率
[1]要查询的数据库表中包含大量字段,这时只查询OID就会比较快捷
[2]启用了二级缓存,且二级缓存中已经包含了待查询的对象