【Java 开发日记】大家来说一下 Mybatis 的缓存机制

【Java 开发日记】大家来说一下 Mybatis 的缓存机制

目录

核心概览

一级缓存

1. 作用域

2. 工作机制

3. 示例说明

4. 注意事项

二级缓存

1. 作用域

2. 开启与配置

3. 工作机制

4. 示例说明

5. 注意事项

缓存顺序与总结

使用建议


核心概览

  • 一级缓存:默认开启,作用范围在 同一个 SqlSession 内。
  • 二级缓存:需要手动配置开启,作用范围在 同一个 Mapper 命名空间(即同一个 Mapper 接口)内,可以被多个 SqlSession 共享。

一级缓存

1. 作用域
  • SqlSession 级别:当同一个 SqlSession 执行相同的 SQL 查询时,MyBatis 会优先从缓存中获取数据,而不是直接查询数据库。
  • 它是 默认开启 的,无法关闭,但可以配置其作用范围(SESSIONSTATEMENT)。
2. 工作机制
  1. 第一次执行查询后,查询结果会被存储到 SqlSession 关联的一级缓存中。
  2. 在同一个 SqlSession 中,再次执行 完全相同的 SQL 查询(包括语句和参数)时,会直接返回缓存中的对象,而不会去数据库查询。
  3. 如果 SqlSession 执行了 增(INSERT)、删(DELETE)、改(UPDATE) 操作,或者调用了 commit()close()rollback() 方法,该 SqlSession 的一级缓存会被清空。这是为了防止读取到脏数据。
3. 示例说明
// 假设获取的 SqlSession 和 UserMapper
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 第一次查询,会发送 SQL 到数据库User user1 = mapper.selectUserById(1L);System.out.println(user1);// 第二次查询,SQL 和参数完全相同,直接从一级缓存返回,不查询数据库User user2 = mapper.selectUserById(1L);System.out.println(user2);// 判断是否为同一个对象(是,因为从缓存中返回的是同一个对象的引用)System.out.println(user1 == user2); // 输出:true// 执行一个更新操作mapper.updateUser(user1);// 此时,一级缓存被清空// 第三次查询,因为缓存被清空,会再次发送 SQL 到数据库User user3 = mapper.selectUserById(1L);System.out.println(user3 == user1); // 输出:false (虽然是同一条数据,但已是新对象)
}
4. 注意事项
  • 对象相同:一级缓存返回的是 同一个对象的引用,因此在同一个 SqlSession 内,你操作的都是同一个 Java 对象。
  • 分布式环境:一级缓存无法在多个应用服务器之间共享,因为它绑定在单个请求的 SqlSession 上。

二级缓存

1. 作用域
  • Mapper 级别 / Namespace 级别:多个 SqlSession 在访问同一个 Mapper 的查询时,可以共享其缓存。
  • 它是 默认关闭 的,需要在全局配置中开启,并在具体的 Mapper XML 中显式配置。
2. 开启与配置

a. 全局配置文件 (mybatis-config.xml)
必须显式设置开启二级缓存(虽然默认是 true,但显式声明是个好习惯)。




b. Mapper XML 文件
在需要开启二级缓存的 Mapper.xml 中添加 <cache/> 标签。


  • <cache/> 标签属性:

    • eviction:缓存回收策略。

      • LRU(默认):最近最少使用。

      • FIFO:先进先出。

      • SOFT:软引用,基于垃圾回收器状态和软引用规则移除。

      • WEAK:弱引用,更积极地移除。

    • flushInterval:缓存刷新间隔(毫秒),默认不清空。

    • size:缓存存放多少元素。

    • readOnly:是否为只读。

      • true:返回相同的缓存对象实例,性能好,但不允许修改。

      • false(默认):通过序列化返回缓存对象的拷贝,安全,性能稍差。

3. 工作机制
  1. 当一个 SqlSession 执行查询后,在关闭或提交时,其查询结果会被存入二级缓存。
  2. 另一个 SqlSession 执行相同的查询时,会先从二级缓存中查找数据。如果找到,则直接返回,否则再去数据库查询。
  3. 任何一个 SqlSession 执行了 增、删、改 操作并 commit() 后,会清空 整个对应 Mapper 的二级缓存,以保证数据一致性。
4. 示例说明
// 第一个 SqlSession
try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);User user1 = mapper1.selectUserById(1L); // 查询数据库sqlSession1.close(); // 关闭时,数据存入二级缓存
}
// 第二个 SqlSession(与第一个不同)
try (SqlSession sqlSession2 = sqlSessionFactory.openSession()) {UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);// 查询相同的 SQL,直接从二级缓存获取,不查询数据库User user2 = mapper2.selectUserById(1L);
}
// 第三个 SqlSession,执行了更新
try (SqlSession sqlSession3 = sqlSessionFactory.openSession()) {UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);User user = mapper3.selectUserById(1L);user.setName("New Name");mapper3.updateUser(user); // 执行更新sqlSession3.commit(); // 提交时,清空 UserMapper 的二级缓存
}
// 第四个 SqlSession
try (SqlSession sqlSession4 = sqlSessionFactory.openSession()) {UserMapper mapper4 = sqlSession4.getMapper(UserMapper.class);// 因为缓存已被清空,所以会再次查询数据库User user4 = mapper4.selectUserById(1L);
}
5. 注意事项
  • 实体类序列化:如果二级缓存的 readOnly="false",那么对应的实体类必须实现 Serializable 接口。
  • 事务提交:只有在 SqlSession 执行 commit()close() 时,数据才会从一级缓存转存到二级缓存。
  • 缓存粒度:二级缓存是 Mapper 级别的,有时会显得比较粗粒度。可以通过 <cache-ref> 让多个 Mapper 共享一个缓存,但不推荐,容易引起数据混乱。

缓存顺序与总结

当发起一个查询请求时,MyBatis 的缓存查询顺序是:

  1. 先查二级缓存:查看当前 Mapper 的二级缓存中是否有数据。
  2. 再查一级缓存:如果二级缓存没有,再查看当前 SqlSession 的一级缓存中是否有数据。
  3. 最后查数据库:如果两级缓存都没有,才发送 SQL 语句到数据库执行查询。

查询到的数据会 先存入一级缓存,在 SqlSession 关闭或提交时,再转存到二级缓存

特性

一级缓存

二级缓存

作用域

SqlSession

Mapper (Namespace)

默认状态

开启

关闭

是否共享

否,Session 独享

是,跨 Session 共享

清空时机

UPDATE/INSERT/DELETE, commit(), close()

同 Mapper 的 UPDATE/INSERT/DELETE + commit()

使用建议

  • 查询多,修改少的数据适合使用二级缓存,如字典表、配置项。
  • 数据实时性要求高的场景(如交易、订单)应谨慎使用二级缓存,或者设置较短的刷新间隔。
  • 在分布式环境中,默认的二级缓存(基于内存)是无法共享的,需要集成 Redis、Ehcache 等第三方缓存中间件来替代。
  • 理解缓存机制有助于解决一些“诡异”的问题,比如在同一个事务中,先后查询和更新,但由于一级缓存的存在,后续查询可能看不到其他线程的更新。

如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!