0%

hibernate缓存

Hibernate 缓存机制深度解析:一级缓存、二级缓存与查询缓存的实践

Hibernate 缓存是提升查询性能的核心机制,通过减少数据库访问次数,显著降低系统 IO 开销。Hibernate 提供一级缓存(Session 缓存)二级缓存(SessionFactory 缓存)查询缓存三级缓存体系,各级缓存的作用范围、管理方式和适用场景差异显著。本文系统解析各级缓存的原理、配置、使用方式及最佳实践,帮助开发者合理利用缓存优化系统性能。

Hibernate 缓存体系概述

Hibernate 缓存按 “作用范围” 和 “管理粒度” 分为三级,各级缓存的核心定位如下:

缓存级别 作用范围 管理主体 启用方式 核心作用
一级缓存 Session(事务级) Hibernate 自动 强制启用,无法关闭 确保同一事务内重复查询同一对象时无需访问数据库
二级缓存 SessionFactory(进程级) 第三方缓存插件 手动配置启用 共享多事务 / 多 Session 的查询结果,减少重复查询
查询缓存 SessionFactory(进程级) 第三方缓存插件 手动配置 + 代码标记 缓存 HQL/QBC 查询结果,避免重复执行相同查询

一级缓存(Session 缓存)

一级缓存是 Hibernate 的内置缓存,与 Session 生命周期绑定,属于 “事务级缓存”,是 Hibernate 确保事务一致性和减少数据库访问的基础。

核心原理

  • 存储内容:当前 Session 加载的持久化对象(包含 OID 和属性值);
  • 生命周期:随 Session 创建而初始化,随 Session 关闭 / 清理(clear())而销毁;
  • 强制启用:无需配置,Hibernate 自动管理,无法手动关闭;
  • 核心价值:同一 Session 内多次查询同一 OID 的对象时,仅第一次访问数据库,后续直接从缓存获取,避免重复 SQL 执行。

一级缓存的关键操作

一级缓存通过 Session 的核心方法实现缓存管理,常见操作如下:

操作方法 作用描述
get()/load() 加载对象时,先检查缓存:存在则直接返回,不存在则查询数据库并放入缓存
save()/update() 执行保存 / 更新时,先更新缓存中的对象状态,事务提交时同步到数据库
flush() 同步缓存中的对象状态到数据库(不清空缓存),确保缓存与数据库一致
clear() 清空缓存中所有对象,所有持久化对象变为游离状态
evict(Object obj) 从缓存中移除指定对象,该对象变为游离状态
contains(Object obj) 判断对象是否在缓存中

一级缓存实践示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

try {
// 1. 第一次查询:缓存中无对象,执行 SELECT SQL,将对象放入缓存
User user1 = session.get(User.class, 1L);
System.out.println("第一次查询:执行 SQL");

// 2. 第二次查询同一 OID 对象:直接从缓存获取,不执行 SQL
User user2 = session.get(User.class, 1L);
System.out.println("第二次查询:从缓存获取,user1 == user2:" + (user1 == user2)); // true

// 3. 清理缓存:移除所有对象
session.clear();
System.out.println("清理缓存后,缓存是否包含 user1:" + session.contains(user1)); // false

// 4. 第三次查询:缓存已清空,重新执行 SQL
User user3 = session.get(User.class, 1L);
System.out.println("第三次查询:重新执行 SQL,user1 == user3:" + (user1 == user3)); // false

tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}

输出结果

1
2
3
4
第一次查询:执行 SQL
第二次查询:从缓存获取,user1 == user2:true
清理缓存后,缓存是否包含 user1:false
第三次查询:重新执行 SQL,user1 == user3:false

一级缓存的注意事项

  • 线程安全:一级缓存与 Session 绑定,Session 非线程安全,因此缓存也不支持多线程共享;
  • 内存控制:处理大量数据(如批量插入 10 万条记录)时,需定期调用 flush() + clear() 清理缓存,避免内存溢出;
  • 脏检查依赖:一级缓存是 Hibernate 脏检查的基础 ——Session 关闭或事务提交时,Hibernate 对比缓存中对象与数据库记录的差异,自动执行 UPDATE 同步修改。

二级缓存(SessionFactory 缓存)

二级缓存是进程级缓存,与 SessionFactory 生命周期绑定,可被所有 Session 共享,适用于 “高频读、低频写” 的数据(如字典表、静态配置数据)。Hibernate 本身不实现二级缓存,需集成第三方缓存插件(如 EHCache、Redis)。

支持的第三方缓存插件

Hibernate 官方推荐的二级缓存实现如下,需根据部署环境(单机 / 集群)选择:

缓存插件 适用场景 支持集群 支持查询缓存 特点
EHCache 单机 / 分布式 是(需配置) 轻量、易用,支持内存 / 磁盘存储,默认推荐
OSCache 单机 支持定时过期,适合静态数据
SwarmCache 集群 基于 JGroups 实现集群同步,不支持查询缓存
JBossCache 集群 支持事务一致性,适合分布式事务场景

二级缓存的核心配置(以 EHCache 为例)

(1)引入依赖(Maven)
1
2
3
4
5
6
7
8
9
10
11
12
<!-- Hibernate-EHCache 集成依赖 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.4.32.Final</version>
</dependency>
<!-- EHCache 核心依赖 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
(2)配置 Hibernate 启用二级缓存(hibernate.cfg.xml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 1. 启用二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>

<!-- 2. 指定二级缓存实现类(EHCache) -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

<!-- 3. 配置 EHCache 配置文件路径(默认读取 classpath:ehcache.xml) -->
<property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>

<!-- 4. 配置需要缓存的实体类(方式1:在 cfg.xml 中全局配置) -->
<class-cache class="com.example.model.User" usage="read-write"/>
<class-cache class="com.example.model.Dict" usage="read-only"/>

<!-- 5. 配置关联集合的缓存(如 Customer 的 orders 集合) -->
<collection-cache collection="com.example.model.Customer.orders" usage="read-write"/>
(3)配置 EHCache 缓存策略(ehcache.xml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

<!-- 默认缓存配置(所有未单独配置的缓存使用此策略) -->
<defaultCache
maxEntriesLocalHeap="10000" <!-- 堆内存最大缓存对象数 -->
eternal="false" <!-- 是否永久缓存(true=永不过期) -->
timeToIdleSeconds="120" <!-- 空闲时间(秒):超过此时间未访问则过期 -->
timeToLiveSeconds="180" <!-- 存活时间(秒):缓存创建后超过此时间过期 -->
diskSpoolBufferSizeMB="30" <!-- 磁盘缓存缓冲区大小 -->
maxEntriesLocalDisk="10000000" <!-- 磁盘最大缓存对象数 -->
diskExpiryThreadIntervalSeconds="120" <!-- 磁盘过期检查线程间隔 -->
memoryStoreEvictionPolicy="LRU" <!-- 内存满时的淘汰策略:LRU(最近最少使用) -->
statistics="true"/> <!-- 启用统计信息 -->

<!-- 自定义缓存策略(针对 User 类) -->
<cache name="com.example.model.User" <!-- 缓存区域名:与实体类全类名一致 -->
maxEntriesLocalHeap="5000"
eternal="false"
timeToIdleSeconds="3600" <!-- 1小时空闲过期 -->
timeToLiveSeconds="86400" <!-- 24小时存活过期 -->
memoryStoreEvictionPolicy="LRU"/>

<!-- 自定义缓存策略(针对 Customer.orders 集合) -->
<cache name="com.example.model.Customer.orders"
maxEntriesLocalHeap="1000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="3600"/>
</ehcache>

二级缓存的并发策略(usage 属性)

class-cachecollection-cacheusage 属性定义缓存的并发访问策略,需根据数据读写频率选择:

并发策略 适用场景 核心特点
read-only 数据永不修改(如字典表、静态配置) 性能最优,不支持更新,修改会抛异常
read-write 数据高频读、低频写(如用户信息) 支持更新,通过版本控制确保缓存与数据库一致性
nonstrict-read-write 数据低频修改,允许短暂不一致(如统计数据) 不保证实时一致性,更新时直接清除缓存,性能较好
transactional 分布式事务场景(如 JTA 事务) 支持事务隔离,确保跨事务的缓存一致性,性能较低

二级缓存的实践示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 第一个 Session:加载 User 并放入二级缓存
Session session1 = sessionFactory.openSession();
Transaction tx1 = session1.beginTransaction();
User user1 = session1.get(User.class, 1L); // 执行 SQL,放入一级和二级缓存
tx1.commit();
session1.close(); // 一级缓存销毁,二级缓存保留 User 对象

// 第二个 Session:从二级缓存获取 User,无需执行 SQL
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
User user2 = session2.get(User.class, 1L); // 从二级缓存加载,不执行 SQL
System.out.println("两个 Session 的 User 是否为同一实例:" + (user1 == user2)); // false(一级缓存隔离,二级缓存共享数据)
tx2.commit();
session2.close();

关键结论

  • 二级缓存共享 “数据内容”,但不同 Session 加载的对象是不同实例(一级缓存隔离);
  • 第二个 Session 无需执行 SQL,直接从二级缓存获取数据,提升查询效率。

二级缓存的注意事项

  • 适用数据类型:仅缓存 “高频读、低频写” 的数据,避免缓存频繁失效(如实时订单数据不适合缓存);
  • 关联集合缓存:缓存集合(如 Customer.orders)时,仅缓存集合的 OID 列表,需同时配置集合元素的 class-cache(如 Order 类),否则访问集合元素时仍需查询数据库;
  • 集群环境配置:分布式场景下需配置缓存同步(如 EHCache 结合 RMI/JGroups),避免集群节点间缓存不一致;
  • 性能监控:通过 EHCache 统计信息(如命中率)优化缓存策略,命中率低于 70% 时需调整缓存配置或禁用缓存。

查询缓存(Query Cache)

查询缓存是二级缓存的扩展,专门缓存 HQL/QBC 查询的结果集(如 from User where age > 18 的查询结果),与 SessionFactory 绑定,可被所有 Session 共享。

核心原理

  • 存储内容:查询语句(如 HQL)+ 查询参数 → 结果集的 OID 列表(而非完整对象);
  • 依赖二级缓存:查询缓存仅存储 OID,获取完整对象时需从二级缓存加载,若二级缓存无对应对象,仍会查询数据库;
  • 启用条件:需同时满足 “配置启用” 和 “代码标记 setCacheable(true)”。

查询缓存的配置

(1)启用查询缓存(hibernate.cfg.xml)
1
2
3
4
5
6
<!-- 1. 启用二级缓存(查询缓存依赖二级缓存) -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 2. 启用查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>
<!-- 3. 配置二级缓存实现(如 EHCache) -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
(2)代码中标记查询可缓存

通过 query.setCacheable(true) 标记查询结果需要缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

try {
// 1. 第一次查询:执行 SQL,结果 OID 存入查询缓存,对象存入二级缓存
List<User> userList1 = session.createQuery("from User where age > :minAge", User.class)
.setParameter("minAge", 18)
.setCacheable(true) // 标记查询结果可缓存
.list();
System.out.println("第一次查询:执行 SQL");

// 2. 第二次执行相同查询:从查询缓存获取 OID,从二级缓存加载对象,不执行 SQL
List<User> userList2 = session.createQuery("from User where age > :minAge", User.class)
.setParameter("minAge", 18)
.setCacheable(true)
.list();
System.out.println("第二次查询:从缓存获取,结果集大小一致:" + (userList1.size() == userList2.size())); // true

tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}

查询缓存的注意事项

  • 查询参数敏感:查询语句或参数不同,会被视为不同查询(如 age > 18age > 20 是两个独立缓存项);
  • 缓存失效机制:当缓存的实体类(如 User)发生 insert/update/delete 时,Hibernate 会自动清除相关的查询缓存,避免脏数据;
  • 不适合动态数据:频繁修改的数据(如实时订单)的查询缓存命中率低,反而增加缓存维护开销,建议禁用;
  • 统计查询慎用count(*)sum() 等聚合查询的结果缓存意义不大(数据易变),除非是低频更新的统计数据。

缓存使用的最佳实践

1. 一级缓存优化

  • 批量操作必清理:处理大量数据(如批量插入 10 万条记录)时,每处理一批(如 30 条)调用 flush() + clear(),避免内存溢出;
  • 避免长时间持有 Session:Web 应用中 “一个请求一个 Session”,请求结束后立即关闭 Session,释放缓存内存。

2. 二级缓存优化

  • 精准选择缓存数据:仅缓存 “高频读、低频写” 的数据(如字典表、用户基本信息),避免缓存 “高频写” 数据(如订单、支付记录);
  • 合理设置过期时间:根据数据更新频率调整 timeToIdleSecondstimeToLiveSeconds(如字典表可设置永久缓存,用户信息设置 24 小时过期);
  • 集群环境同步:分布式部署时,使用支持集群同步的缓存插件(如 EHCache + JGroups),确保各节点缓存一致性。

3. 查询缓存优化

  • 仅缓存高频固定查询:如 “首页热门商品列表”“字典表查询” 等高频且参数固定的查询;
  • 结合二级缓存:确保查询结果对应的实体类已配置二级缓存,否则查询缓存仅存储 OID,仍需频繁访问数据库;
  • 禁用不必要的查询缓存:动态条件查询(如用户个性化筛选)的缓存命中率低,建议不启用 setCacheable(true)

4. 缓存命中率监控

通过 EHCache 的统计功能监控缓存命中率,公式为:
命中率 = 缓存命中次数 / (缓存命中次数 + 缓存未命中次数)

  • 命中率 > 80%:缓存有效,无需调整;
  • 命中率 < 70%:需优化缓存策略(如调整过期时间、更换缓存数据、禁用低命中率缓存)。

总结

Hibernate 缓存体系的核心是 “分层缓存、按需使用”:

  • 一级缓存:内置强制启用,确保事务内查询效率,需注意内存控制;
  • 二级缓存:第三方插件实现,进程级共享,适合高频读数据,需合理选择并发策略;
  • 查询缓存:二级缓存的扩展,缓存查询结果 OID,需结合 setCacheable(true) 启用

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10