一,什么是缓存?
缓存是指为了降低应用程序对物理数据源访问的频次从而提高应用程序的运行性能的一种策略,它位于应用程序和永久性数据存储源之间用于临时存放复制数据的内存区域
1.缓存的范围:缓存范围决定了缓存的生命周期,缓存范围分为3类:
(1)事务范围:缓存只能被当前事务访问,缓存的生命周期依赖于事务的生命周期,事务结束时,缓存的生命周期也结束了;
(2)进程范围:缓存被进程内的所有事务共享,这些事务会并发访问缓存,需要对缓存采用必要的事务隔离机制,缓存的生命周期取决与进程的生命周期,进程结束,缓存的生命周期也结束了;
(3)集群范围:缓存被一个或多个计算机的进程共享,缓存中的数据被复制到集群中的每个进行节点,进程间通过远程通信来保证缓存中数据的一致性;
在查询时,如果在事务范围内的缓存中没有找到,可以到进程范围或集群范围的缓存中查找,如果还没找到,则到数据
2.Hibernate缓存分为两种:分别为一级缓存(Session Level 也成为内部缓存)和二级缓存(SessionFactory Level)
二,一级缓存
1.Session 缓存:
Hibernate的一级缓存属于Session级缓存,所以它的生命周期与Session相同(随Session的创建而创建,随Session的销毁而销毁)
当程序使用Session加载持久化对象,Session会根据加载的数据类和唯一性标识在缓存中查找是否存在此对象的缓存实例,如果存在将其作为结果返回,否则Session会继续向二级缓存中查找实例对象
在Hibernate中不同的Session不能共享一级缓存,一个Session不能访问其他Session在一级缓存中对象缓存实例。
2.一级缓存中常见的操作:
->flush
(1) 刷新缓存(flush):Session能够在某些时间点按照缓存中对象的变化来执行相关的SQL语句,来同步更新数据库
(2)默认情况下,Session在以下时间点刷险缓存:
- 显示调用Session的flush()方法
- 当应用程序调用Transaction的commit()方法的时,该方法先flush,在向数据库提交事务
- 当应用程序执行一些查询(HQL,Criteria)操作时,如果缓存中持久化对象的属性已经发生了变化,会先flush缓存,以保证查询结果能够反映持久化对象的的最新状态
(3)commit() 和 flush() 方法的区别:flush 执行一系列 sql 语句,但不提交事务;commit 方法先调用flush() 方法,然后提交事务. 意味着提交事务意味着对数据库操作永久保存下来。
(4)改变flush的默认时间点,可以通过Session的setFlushMode()方法显示设定flush的时间点
package com.Test;
import com.Product.Product;
import com.hibernate.HibernateInitialize;
import org.hibernate.Session;
public class GetProduct {
public static void main(String[] args)
{
Session session=null;
try{
session= HibernateInitialize.getSession();
Product product1=(Product)session.get(Product.class,new Integer(0));
System.out.println("第一装载对象");
Product product=(Product)session.get(Product.class,new Integer(0));
System.out.println("第二次装载对象");
}catch (Exception e)
{
System.out.println("对象装载失败");
}finally {
HibernateInitialize.closeSession();
}
}
}
执行代码,截图:
从控制台输出的信息可以看出,Hibernate只访问了一次数据库,第二次加载对象是从一级缓存中将该对象的缓存实例以结果的形式返回
->clear():清除所有对象的一级缓存,对对象所做的修改,全都清除
->evict():清除一级缓存指定对象
三,二级缓存
Hibernate的二级缓存又被称为“SessionFactory的缓存”,由于这个SessionFactory对象的生命周期和应用程序相对应。因此二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,需要采用适当的并发访问策略,该策略为缓存的数据提供了事务隔离级别
1.为什么要采用事务的隔离级别?
对于同时运行的多个事务,当这些事务访问数据库中相同的数据时候,如果没有采用必要的隔离机制,会导致各种并发问题:
- 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
- 不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
- 幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
2.一个事务与其他事务的隔离程度成为隔离级别。数据库规定了多种事务的隔离级别。不同的隔离级别对应不同的干扰程度,隔离级别越高,数据的一致性就越好,但并发性越弱。
3.设置隔离级别
(1).在MySql中设置隔离级别:
每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别. MySQL 默认的隔离级别为 Repeatable Read
- 查看当前的隔离级别: SELECT @@tx_isolation;
- 设置当前 mySQL 连接的隔离级别: set transaction isolation level read committed;
- 设置数据库系统的全局的隔离级别:set global transaction isolation level read committed;
(2)在Hibernate中设置隔离级别
JDBC 数据库连接使用数据库系统默认的隔离级别. 在 Hibernate 的配置文件中可以显式的设置隔离级别. 每一个隔离级别都对应一个整数:
1. READ UNCOMMITED:非严格读写-不保证缓存与数据库中数据一致性。对于极少被修改,而且允许脏读的数据,可以采用。
2. READ COMMITED:读写型-对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为防止脏读
4. REPEATABLE READ:事务型-仅在受管理的环境下适用。对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为可以防止脏读和不可重复读
8. SERIALIZEABLE:只读型-提供Serializable数据隔离级别,对于从不会被修改的数据,可以采用这种隔离类型
Hibernate 通过为 Hibernate 映射文件指定 hibernate.connection.isolation 属性来设置事务的隔离级别
4.适合存放在二级缓存中的数据:很少被修改的数据,不是很重要的数据,允许出现偶尔并发的数据,不会被并发访问的数据,常量数据。
5.当程序使用Session加载持久化对象时,Session首先会根据加载的数据类和唯一性标识在缓存中查找是否存在此对象的缓存实例。如果存在将其作为结果返回,否则Session会继续向二级缓存中查找实例对象。如果二级缓存也无匹配对象,将直接访问数据库。
由于Hibernate本身并未提供二级缓存的产品化实现,所以需要引入第三方插件实现二级缓存。
6.使用EHCache缓存插件为例,说明配置方法及步骤:
(1)引入EHCache相关的jar包:lib\optional\ehcache下的三个包
(2)创建EHCache配置文件ehcache.xml
<ehcache>
<diskStore path="java.io.tmpdir"/> //设置缓存数据文件的存储目录
<defaultCache //设置缓存的默认数据过期策略,每个命名缓存代表一个缓存区域。
maxElementsInMemory="10000" //设置缓存对象的最大数目
eternal="false" //指定是否永不过期,true为不过期
timeTodleSeconds="120" //设置对象处于空闲状态的最大秒数
timeToLiveSeconds=‘120’ //设置对象处于缓存状态的最大秒数
overflowToDisk="true" //设置内存溢出时是否将溢出对象写入硬盘
/>
</ehcache>
(3)在Hibernate配置文件hibernate.cfg.xml配置开启二级缓存
<session-factory>
.....
<!--开启二级缓存--->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 设置二级缓存插件EHCache的Provider类 -->
<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.EhCacheRegionFactory</property></strong></span> </session-factory>
(4)配置哪些实体类的对象需要二级缓存,有两种方式:
a: 在实体类的映射文件中配置。
<hibernate-mapping>
<class name=" " table=" ">
<cache usahe="read-only"/>
....
</class>
</hibernate-mapping>
映射文件中使用<cache>元素设置持久化类User的二级缓存并发访问策略,usage属性取值:read-only表示只读型并发访问策略;read-write表示读写型并发访问策略;nonstrict-read-wriite表示非严格读写型并发访问策略
注意:<cache>元素只能放在<class>元素的内部,而且必须放在<id>元素的前面,<cache>元素放在哪个class元素下面,就说明对哪个类进行缓存
b: 在hibernate.cfg.xml文件中使用<class-cache>元素来配置哪些实体类的对象需要二级缓存
注意:<class-cache>元素必须放在所有<mapping>元素后面
四,查询缓存
对于经常使用的查询语句, 如果启用了查询缓存, 当第一次执行查询语句时, Hibernate 会把查询结果存放在查询缓存中. 以后再次执行该查询语句时, 只需从缓存中获得查询结果, 从而提高查询性能
(1)查询缓存使用于如下场合:
- 应用程序运行时经常使用查询语句
- 很少对与查询语句检索到的数据进行插入, 删除和更新操作
- 配置二级缓存, 因为查询缓存依赖于二级缓存
- 在 hibernate 配置文件中启用查询缓存
- 对于希望启用查询缓存的查询语句, 调用 Query 的 setCacheable() 方法
- T1 时刻执行查询操作, 把查询结果存放在 QueryCache 区域, 记录该区域的时间戳为 T1
- T2 时刻对查询结果相关的表进行更新操作, Hibernate 把 T2 时刻存放在 UpdateTimestampCache 区域.
- T3 时刻执行查询结果前, 先比较 QueryCache 区域的时间戳和 UpdateTimestampCache 区域的时间戳, 若 T2 >T1, 那么就丢弃原先存放在 QueryCache 区域的查询结果, 重新到数据库中查询数据, 再把结果存放到 QueryCache 区域; 若 T2 < T1, 直接从 QueryCache 中获得查询结果