转载地址:https://www.cnblogs.com/qjjazry/p/6291466.html
hibernate的缓存可分为一级缓存和二级缓存
从缓存范围可分为事物范围缓存、应用范围缓存、集群范围缓存,
事物范围缓存(单session,即一级缓存)
事物范围缓存只能被当前事务访问,每个事物都有各自的缓存。缓存的生命周期依赖于事务。当事务结束,缓存的生命周期也结束了。事务范围的缓存使用内存作为缓存介质。
应用范围缓存(单sessionFactory,即二级缓存)
应用范围的缓存可也被应用程序中的所有事物所共享,生命周期依赖于应用程序。当应用程序结束,缓存的生命周期也随之结束。应用范围缓存使用内存或硬盘作为缓存介质。
集群范围缓存(多个sessionFactory)
在集群环境中,缓存被一个机器或多个机器的进程共享,缓存中的数据被复制到集群环境中的每个进程节点,进程之间通过远程通信保证数据的一致性。缓存中的数据采用对象的松散数据形式。有些hibernate的二级缓存第三方插件支持集群范围缓存。
一级缓存
一级缓存,即session缓存,其实就是一块内存中的空间,这片空间放了java相关的对象。session缓存是事务级缓存,伴随事务的开始而开始,伴随事务的结束而结束。session缓存是有hibernate管理的,是hibernate内置,不可取消。
当程序调用session的load、get、save、saveOrUpdate、update方法或查询接口时,hibernate会对对象进行缓存。当通过load或get方法查询实体对象时,hibernate会首先到缓存中查询,在找不到实体对象时,才会发出sql语句到db中查询。
一级缓存相关的方法
evict(Object obj)从session中删除对象
clear() 清除session缓存
contains(Object obj) 判断对象是否在session缓存中
flush() session中对象同步数据库
@Test
2 public void test01_SQL() {
3 //1. 获取Session
4 Session session = HbnUtils.getSession();
5 try {
6 //2. 开启事务
7 session.beginTransaction();
8 //3. 操作
9 //session.get的时候,hibernate就将数据库中的数据加载到session缓存中,同时也会备份到快照中
10 Student student = session.get(Student.class, 2);
11 student.setName("n_2");
12 session.update(student);
13 //4. 事务提交
14 //commit是唯一的同步时间点,当程序运行到同步时间点时,
15 //hibernate先判断session缓存中的数据和快照中的数据是否相同,
16 //如果不相同,则更新数据库,如果相同,则不更新数据库,
17 //以上,两种情况,无论是否写了update,都成立
18 session.getTransaction().commit();
19 } catch (Exception e) {
20 e.printStackTrace();
21 //5. 事务回滚
22 session.getTransaction().rollback();
23 }
24 }
session的刷新和同步
sessio的刷新是指session中缓存的数据的更新,session同步是指将session中的对象数据同步更新到数据库。执行同步的时间点只有一个即事务的提交。
当执行对session中数据的更改操作是,即update和delete,缓存中的数据并不会立刻刷新,而是在一定的时间点才会刷新,更新缓存中的数据。刷新的时间点
有三个:执行query查询、执行session.flush()、执行事务的提交session.comit().
通过session.setFluhMode()可以设置缓存刷新模式

![]()
注意:增删改操作,当刷新时间点到来时是否马上进行缓存更新,各自情况还是不同的。
删除操作,一到达刷新时间点,马上执行delete语句,更新Session中数据;
更新操作,到达刷新时间点后,是否马上执行update语句,更新Session中数据,还要看该修改内容是否与快照一致若一致,则执行,否则,则不执行;
插入操作,无需等到刷新时间点的到达,见到save()后马上执行insert语句。因为插入操作不是修改Session中已经的存在数据,而是给Session中添加数据。
二级缓存
二级缓存是sessionFactory级的缓存,其生命周期与sessionFactory一样,sessionFactory缓存可以根据功能和目的分为内置缓存和外置缓存。
sessionFactory的内置缓存存放了映射元数据和预定义sql, 映射元数据是映射文件的副本,预定义sql是hibernate初始化时根据映射元数据推导出来的sql,SessionFactory的内置缓存是只读的,程序无法修改缓存中的映射元数据和预定义sql,因此sessionFactory不需要做内置缓存和映射文件的同步。
session的外置缓存是一个可配置的插件,在默认情况下,hibernate是不会启动这个插件的。外置缓存的数据是数据库数据的副本,外置缓存的介质可以是硬盘或内存,sessionFactory外置缓存也被称为hibernate的二级缓存。
hibernate本身只提供了二级缓存的规范,并未实现,需要添加第三方缓存插件,常用的缓存插件有:EHCache、MemCached、OSCache、SwarmCache、JBossCache。
这些插件 各有侧重,各有特点:
![]()

二级缓存的执行:
当hibernate根据id访问数据对象时,首先会从一级缓存中查找,如果一级缓存中没有且配置了二级缓存的话,则去二级缓存中查找。若还找不到,则前往数据库中查找,,把结果按id放到缓存中,执行增删改时会同步更新缓存。
二级缓存的内容分类
1、类缓存:缓存对象为类对象 2、集合缓存:缓存对象为集合对象 3、更新时间戳缓存 4、查询缓存:缓存对象为查询结果
二级缓存的并发策略
事务性(transactional):隔离级别最高,对于经常被读但很少被改的数据,可以采用此策略。因为它可与防止脏读和不可重复读的并发问题,发生异常时,可以进行回滚(系统开销大)。
读写型(read-write):对于经常被读且很少修改的数据,可以采用此策略。因为可以防止脏读的并发问题。更新缓存是会锁定缓存中的数据。
非严格读写型(nonstrict-read-write):不保证缓存数据和数据库数据的一致性,对于极少修改并且允许脏读的数据,可以采用此策略,不锁定缓存中的数据。
只读型(read-only):对于从来不会修改的数据,可以采用此策略。
二级缓存管理的方法
与二级缓存管理有关的方法,都定义在Cache接口中,获取Cache对象,需用通过sessionFactory.getCache()方法。
Cache cache=sessionFactory.getCache();
二级缓存的配置(应用EHCache插件)
1、导入EHCache的jar包,
2、在hibernate.cfg.xml中的<session-factory>元素中加入以下配置,开启二级缓存和注册二级缓存区域工厂
1 <!-- 开启二级缓存 -->
2 <property name="hibernate.cache.use_second_level_cache">
3 true
4 </property>
5 <!-- 注册二级缓存区域工厂 -->
6 <property name="hibernate.cache.region.factory_class">
7 org.hibernate.cache.ehcache.EhCacheRegionFactory
8 </property>
3、解压EHCache的核心jar包ehcache-core2-4-3.jar,将其中的一个配置文件EhCache.failSale.xml直接放到src目录下,并更名为ehcache.xml,注意其中的配置可以修改:
<defaultCache 2 maxElementsInMemory="10000" 3 eternal="false" 4 timeToIdleSeconds="120" 5 timeToLiveSeconds="120" 6 maxElementsOnDisk="10000000" 7 diskExpiryThreadIntervalSeconds="120" 8 memoryStoreEvictionPolicy="LRU"> 9 <persistence strategy="localTempSwap"/> 10 </defaultCache>
4、指定缓存内容
指定哪个类或者哪个集合需要进行二级缓存,指定的位置有两处:映射文件、主配置文件,这两种任选其中一种即可,效果一样,但各有利弊。
在映射文件中指定缓存内容,查看其类的映射文件时,一眼就可以看到类是缓存类,集合是缓存集合。但弊端是缓存的指定位置分散,缺乏项目的整体性。
在主配置文件中指定缓存内容,可以看到整个项目中的缓存类和缓存集合,但指定缓存的内容和类映射分离。
映射文件中指定缓存内容
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC 3 "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 4 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 5 6 <hibernate-mapping package="com.tongji.beans"> 7 <class name="Country"> 8 <!-- 指定当前类为类缓存对象 --> 9 <!-- <cache usage="read-only"/> --> 10 <id name="cid"> 11 <generator class="native"/> 12 </id> 13 <property name="cname"/> 14 <set name="ministers" cascade="save-update"> 15 <!-- 指定当前集合为集合缓存对象 --> 16 <!-- <cache usage="read-only"/> --> 17 <key column="countryId"/> 18 <one-to-many class="Minister"/> 19 </set> 20 </class> 21 </hibernate-mapping>
在主配置文件中指定缓存内容,在<maping>标签后指定缓存类和缓存集合。
<!-- 指定类缓存 --> 2 <class-cache usage="read-only" class="com.tongji.beans.Minister" /> 3 <class-cache usage="read-only" class="com.tongji.beans.Country" /> 4 <!-- 指定集合缓存 --> 5 <collection-cache usage="read-only" 6 collection="com.tongji.beans.Country.ministers" />
补充:(1)类缓存:缓存的是类的详情; 集合缓存:在没有对集合所在的类进行缓存是,缓存的是都有元素的id。
(2)、query查询缓存,即session.createQuery(hql)时进行缓存。说明一下三点
1、query查询的结果默认会放到一二级缓存中,
2、query查询默认不是从一二级缓存中读取数据,但可以通过配置去改变。在主配置文件中添加如下配置
<property name="hibernate.cache.use_query_cache">true</property>,查询时的代码如下
1 @Test 2 public void test02() { 3 //1. 获取Session 4 Session session = HbnUtils.getSession(); 5 try { 6 //2. 开启事务 7 session.beginTransaction(); 8 //3. 操作 9 //第一次查询 10 String hql = "from Country where cid=1"; 11 Country country1 = (Country) session.createQuery(hql).setCacheable(true).uniqueResult(); 12 System.out.println("第一次查询:Country = " + country1); 13 //第二次查询 14 Country country2 = (Country) session.createQuery(hql).setCacheable(true).uniqueResult(); 15 System.out.println("第二次查询:Country = " + country2); 16 17 //将一级缓存数据清空 18 session.clear(); 19 20 //第三次查询,从二级缓存中读取 21 Country country3 = (Country) session.createQuery(hql).setCacheable(true).uniqueResult(); 22 System.out.println("第三次查询:Country = " + country3); 23 //4. 事务提交 24 session.getTransaction().commit(); 25 } catch (Exception e) { 26 e.printStackTrace(); 27 //5. 事务回滚 28 session.getTransaction().rollback(); 29 } 30 }
(3)query要从缓存中读取数据,必须保证query所执行的hql语句一致,应为query查询不仅将数据放进缓存,还将hql语句放入缓存。
(4)修改时间戳:二级缓存比一级缓存多了一个属性,updateTimeStamp,修改时间戳,只要这个属性发生了改变,就说明数据库中的数据发生了改变,二级缓存中的数据不 是最新数据,需要从数据库中重新查询更新数据。query的excuteUpdate方法会绕过一级缓存修改二级缓存对象的updateTimeStamp值,该值的改变会导致二级缓存会 通过查询更新数据(一二级缓存全更新了)。