java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Mybatis与Hibernate缓存

Mybatis与Hibernate缓存的具体使用

作者:Listen·Rain

本文主要介绍Mybatis与Hibernate缓存的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、Mybatis与Hibernate缓存对比

1、Hibernate 缓存机制

Hibernate 的缓存体系分为一级缓存和二级缓存两层,核心作用是减少对数据库的直接访问,大幅提升数据查询性能。

2、MyBatis 缓存机制

MyBatis 同样设计了两级缓存体系,但整体实现更轻量化,灵活性更高,同时对开发者的透明度更低。

3、两者核心差异对比

对比维度Hibernate 缓存MyBatis 缓存
自动化程度高,框架自动维护缓存生命周期与数据同步低,需开发者手动控制缓存的更新与失效
数据一致性保障内置完善的并发策略,出现脏数据会主动提示无自动校验,脏数据无任何告警,风险更高
缓存适配场景适配绝大多数业务场景,对多表关联查询支持友好更适合简单单表查询,复杂关联场景易出现一致性问题
配置复杂度二级缓存需额外配置插件与并发策略,上手门槛略高配置简单,仅需少量标签即可开启二级缓存

二、缓存常见错误与陷阱

1、MyBatis 缓存常见错误与陷阱

MyBatis 的缓存机制相对“轻量”且“透明”,这导致开发者往往忽略其底层实现细节,从而引发隐蔽的 Bug。

  1. 一级缓存导致的“脏读”与线程安全问题
    错误场景:在 Web 应用中,错误地将 SqlSession 定义为类的成员变量或静态变量,导致多个请求线程共享同一个 SqlSession
    后果:
// ❌ 错误示范:SqlSession 作为成员变量,被多线程共享
public class UserService {
    private SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();

    public User getUserById(int id) {
        // 高并发下,不同线程可能读取到其他线程未提交的缓存数据
        return sqlSession.selectOne("selectUser", id);
    }
}

正确做法:确保每个线程拥有独立的 SqlSession,通常在 Service 层通过 Spring 管理事务,或使用 try-with-resources 确保 Session 随请求结束而关闭。

  1. Spring 事务中一级缓存失效或累积导致 OOM
    错误场景:
// ❌ 错误示范:长事务中大量查询不同ID,导致一级缓存无限膨胀
@Transactional
public void processLargeData() {
    for (int i = 0; i < 100000; i++) {
        // 每次查询不同ID,结果都会存入当前SqlSession的一级缓存
        orderMapper.selectById(i); 
    }
    // 事务提交前,这10万个对象一直占用内存
}

正确做法:

  1. 二级缓存的“序列化”与“多表关联”陷阱
    错误场景:
<!-- OrderMapper.xml -->
<mapper namespace="com.example.OrderMapper">
    <cache/> <!-- 开启二级缓存 -->
    <select id="getOrderWithItems" resultType="Order">
        SELECT * FROM orders o JOIN items i ON o.id = i.order_id WHERE o.id = {id}
    </select>
</mapper>
<!-- ItemMapper.xml -->
<mapper namespace="com.example.ItemMapper">
    <cache/>
    <update id="updateItem">
        UPDATE items SET name = {name} WHERE id = {id}
    </update>
</mapper>

后果:更新 Item 后,OrderMapper 中的联合查询结果仍然是旧的,因为两个 Mapper 的缓存空间是隔离的。
正确做法:

  1. 微服务架构下的一级缓存误导
    错误场景:在微服务环境中,默认开启一级缓存。如果服务实例 A 查询了数据,随后服务实例 B(或同一实例的其他事务)修改了数据库,实例 A 的后续查询仍可能命中本地一级缓存(如果 SqlSession 未正确关闭或复用),导致数据不一致。
    建议:在 application.yml 中显式配置 local-cache-scope: STATEMENT 以禁用一级缓存,依赖外部缓存或数据库实时性。

2、Hibernate 缓存常见错误与陷阱

Hibernate 缓存更智能,但也更复杂。错误通常源于对“对象状态”和“缓存策略”的误解。

  1. 二级缓存策略选择不当导致数据不一致
    错误场景:对高频更新的数据(如库存、账户余额)使用了 read-onlynonstrict-read-write 策略,甚至在集群环境下未配置正确的并发锁机制。
    后果:
// ❌ 错误配置:对频繁变动的财务数据使用只读缓存
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY) // 严禁用于可写数据
public class AccountBalance { ... }

正确做法:

  1. Query Cache 与 二级缓存配合失误
    错误场景:启用了 Query Cache (hibernate.cache.use_query_cache=true),但未启用二级缓存,或者查询条件中包含动态参数但未正确理解缓存 Key 的生成规则。
    原理:Query Cache 存储的是 查询结果集的 ID 列表,而不是实体对象本身。实体对象仍需从二级缓存或数据库加载。
    后果:
// ❌ 错误用法:期望 Query Cache 能缓存整个对象图
Query query = session.createQuery("from User u where u.age > :age");
query.setParameter("age", 18);
query.setCacheable(true); // 仅缓存了 ID 列表
List<User> users = query.list(); 
// 如果二级缓存没开,这里会根据 ID 再次查库
  1. Session 管理不当导致一级缓存内存泄漏
    错误场景:在一个长生命周期的 Session 中执行大量的 load()list() 操作,且不调用 session.clear()
    后果:Hibernate 一级缓存(Session 级)会持有所有加载过的持久化对象引用。随着操作增多,JVM 堆内存被占满,触发 Full GC 甚至 OOM。
    典型场景:批量导入/导出几万条数据时,使用同一个 Session。
// ❌ 错误示范:批量处理未清理 Session 缓存
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for (int i = 0; i < 50000; i++) {
    User user = session.get(User.class, i); // 每个对象都进入一级缓存
    user.setName("Updated");
    if (i % 50 == 0) {
        session.flush();
        session.clear(); // ✅ 必须定期清理,否则 OOM
    }
}
tx.commit();
session.close();
  1. load()get() 混淆引发的 LazyInitializationException
    错误场景:使用 session.load() 获取对象,该对象是一个代理对象(Proxy)。如果在 Session 关闭后访问其非 ID 属性,会抛出异常。
    原因:load() 优先从缓存查找,若缓存无则返回代理对象,不立即查库。若后续访问属性时 Session 已关闭,无法初始化代理。
    对比:get() 总是立即查库(若缓存无),返回真实对象或 null。
User user = session.load(User.class, 1L); // 返回代理对象,不查库
session.close();
System.out.println(user.getName()); // ❌ 抛出 LazyInitializationException

3、总结与最佳实践建议

维度MyBatis 避坑指南Hibernate 避坑指南
一级缓存微服务/分布式环境下建议禁用 (STATEMENT 模式),避免脏读和内存累积。确保 SqlSession 线程隔离。严格管理 Session 生命周期。批量操作务必定期 clear()。理解 load vs get 的区别。
二级缓存慎用。多表关联 易出脏数据。建议仅在单表、读多写少场景使用,或直接交由 Redis 处理。精选数据。仅缓存不变或极少变的字典/配置数据。避免缓存金融/库存等强一致性数据。
一致性框架不保证跨 Mapper 的一致性。需开发者手动维护或通过外部缓存解决。框架提供较好的并发策略,但配置复杂。需根据事务隔离级别选择合适的 CacheConcurrencyStrategy
调试开启 SQL 日志,观察是否重复执行相同 SQL。监控缓存命中率。开启 Hibernate Statistics,监控 L1/L2 Cache Hit/Miss 比例,识别内存泄漏。

核心原则:

  1. 不要过度依赖框架内置缓存来解决高性能问题,优先考虑业务层面的缓存设计(如 Redis)。
  2. 一致性优于性能:在涉及金钱、库存、状态流转的场景,宁可牺牲性能查库,也不要冒险使用可能导致脏数据的缓存策略。
  3. 监控与测试:上线前必须进行压力测试,监控 JVM 内存使用和缓存命中率,及时发现缓存失效或泄漏问题。

到此这篇关于Mybatis与Hibernate缓存的具体使用的文章就介绍到这了,更多相关Mybatis与Hibernate缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文