hibernate的缓存策略

本文深入解析Hibernate缓存机制,涵盖一级缓存与二级缓存的工作原理、生命周期及配置方法。介绍缓存的不同类型及其应用场景,帮助开发者理解如何有效利用缓存提高应用性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载地址: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值,该值的改变会导致二级缓存会 通过查询更新数据(一二级缓存全更新了)。
















































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值