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) |
判断对象是否在缓存中 |
一级缓存实践示例
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();
}
输出结果:
第一次查询:执行 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)
<!-- 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. 启用二级缓存 -->
<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)
<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-cache 和 collection-cache 的 usage 属性定义缓存的并发访问策略,需根据数据读写频率选择:
| 并发策略 | 适用场景 | 核心特点 |
|---|---|---|
read-only |
数据永不修改(如字典表、静态配置) | 性能最优,不支持更新,修改会抛异常 |
read-write |
数据高频读、低频写(如用户信息) | 支持更新,通过版本控制确保缓存与数据库一致性 |
nonstrict-read-write |
数据低频修改,允许短暂不一致(如统计数据) | 不保证实时一致性,更新时直接清除缓存,性能较好 |
transactional |
分布式事务场景(如 JTA 事务) | 支持事务隔离,确保跨事务的缓存一致性,性能较低 |
二级缓存的实践示例
// 第一个 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. 启用二级缓存(查询缓存依赖二级缓存) -->
<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) 标记查询结果需要缓存:
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 > 18和age > 20是两个独立缓存项); - 缓存失效机制:当缓存的实体类(如
User)发生insert/update/delete时,Hibernate 会自动清除相关的查询缓存,避免脏数据; - 不适合动态数据:频繁修改的数据(如实时订单)的查询缓存命中率低,反而增加缓存维护开销,建议禁用;
- 统计查询慎用:
count(*)、sum()等聚合查询的结果缓存意义不大(数据易变),除非是低频更新的统计数据。
缓存使用的最佳实践
1. 一级缓存优化
- 批量操作必清理:处理大量数据(如批量插入 10 万条记录)时,每处理一批(如 30 条)调用
flush()+clear(),避免内存溢出; - 避免长时间持有 Session:Web 应用中 “一个请求一个 Session”,请求结束后立即关闭 Session,释放缓存内存。
2. 二级缓存优化
- 精准选择缓存数据:仅缓存 “高频读、低频写” 的数据(如字典表、用户基本信息),避免缓存 “高频写” 数据(如订单、支付记录);
- 合理设置过期时间:根据数据更新频率调整
timeToIdleSeconds和timeToLiveSeconds(如字典表可设置永久缓存,用户信息设置 24 小时过期); - 集群环境同步:分布式部署时,使用支持集群同步的缓存插件(如 EHCache + JGroups),确保各节点缓存一致性。
3. 查询缓存优化
- 仅缓存高频固定查询:如 “首页热门商品列表”“字典表查询” 等高频且参数固定的查询;
- 结合二级缓存:确保查询结果对应的实体类已配置二级缓存,否则查询缓存仅存储 OID,仍需频繁访问数据库;
- 禁用不必要的查询缓存:动态条件查询(如用户个性化筛选)的缓存命中率低,建议不启用
setCacheable(true)。
4. 缓存命中率监控
通过 EHCache 的统计功能监控缓存命中率,公式为:命中率 = 缓存命中次数 / (缓存命中次数 + 缓存未命中次数)
- 命中率 > 80%:缓存有效,无需调整;
- 命中率 < 70%:需优化缓存策略(如调整过期时间、更换缓存数据、禁用低命中率缓存)。
总结
Hibernate 缓存体系的核心是 “分层缓存、按需使用”:
- 一级缓存:内置强制启用,确保事务内查询效率,需注意内存控制;
- 二级缓存:第三方插件实现,进程级共享,适合高频读数据,需合理选择并发策略;
- 查询缓存:二级缓存的扩展,缓存查询结果 OID,需结合
setCacheable(true)启用