MyBatis二级缓存跨会话共享

wen java案例 3

本文目录导读:

MyBatis二级缓存跨会话共享

  1. 核心结论
  2. 工作原理(跨会话共享流程)
  3. 如何启用二级缓存(三步走)
  4. 关键特性与注意事项
  5. 二级缓存 vs Redis/外部缓存

这是一个关于MyBatis二级缓存的核心特性问题,二级缓存(Second Level Cache)的设计初衷就是为了跨会话(SqlSession)共享数据

下面我从工作原理、配置、注意事项和实际效果几个方面为你详细解析。

核心结论

是的,MyBatis二级缓存默认就是跨SqlSession共享的。

  • 一级缓存(Local Cache):作用域是 SqlSession,同一个 SqlSession 中,多次执行相同的查询,会从缓存中取。会话关闭或提交后,一级缓存即失效。
  • 二级缓存(Global Cache):作用域是 Mapper 的命名空间(Namespace),不同的 SqlSession 只要查询同一个 Mapper 下的方法,且该数据在缓存中,就能共享使用。

工作原理(跨会话共享流程)

为了充分理解,你可以看下面这个典型场景:

  1. 用户A 开启会话 sqlSession1,执行 select * from user where id = 1
  2. MyBatis 检查二级缓存(namespace=com.example.mapper.UserMapper),没有数据
  3. 去数据库查询,返回结果。
  4. 关键步骤sqlSession1 关闭或提交(session1.close()时,查询结果才会被序列化并存入二级缓存(通常是一个全局的Map)。
  5. 用户B 开启会话 sqlSession2,执行 select * from user where id = 1
  6. MyBatis 检查二级缓存(namespace=UserMapper),发现数据,直接返回缓存对象(注意:是反序列化后的新对象,避免线程安全问题)。
  7. 二级缓存生效,无需查询数据库

如何启用二级缓存(三步走)

要让它正常工作,需要完成以下配置:

第一步:全局配置(mybatis-config.xml)

开启二级缓存的总开关(默认是开启的,但建议显式设置):

<settings>
    <!-- 开启二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

第二步:Mapper 映射文件配置(XXXMapper.xml)

<mapper> 标签内添加 <cache/> 标签,这表示当前命名空间下的所有查询结果都将被缓存。

<mapper namespace="com.example.mapper.UserMapper">
    <!-- 启用二级缓存 -->
    <cache
        eviction="LRU"          <!-- 回收策略:最近最少使用 -->
        flushInterval="60000"   <!-- 刷新间隔:60秒,不写则按SQL执行时机刷新 -->
        size="512"              <!-- 引用数目 -->
        readOnly="false" />     <!-- 是否只读:false表示可读写(需序列化) -->
    <!-- ... select / insert / update / delete ... -->
</mapper>

第三步:实体类实现序列化接口

由于二级缓存需要跨会话存储和读取,通常需要将对象序列化后存储(特别是当 readOnly=false 时)。

public class User implements Serializable {
    private static final long serialVersionUID = 1L; // 强烈建议显式定义
    private Integer id;
    private String name;
    // getters and setters ...
}

关键特性与注意事项

1 缓存刷新(Flush)

  • 任何在同一个Mapper下的 INSERTUPDATEDELETE 操作,都会清空该Mapper下的二级缓存。
  • 目的:防止读到脏数据,这是MyBatis保证最终一致性的简单策略。

2 跨命名空间查询的“坑” (重点)

如果使用多表联合查询UserMapper 中有个查询关联了 Role 表),但在 RoleMapper 中更新了数据,UserMapper 的缓存不会自动失效

解决方案: 使用 cache-ref 标签让两个 Mapper 共享同一个缓存命名空间。

<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 引用 RoleMapper 的缓存,这样更新 Role 时也会刷新 User 的缓存 -->
    <cache-ref namespace="com.example.mapper.RoleMapper"/>
    <!-- ... -->
</mapper>

3 线程安全与对象拷贝

  • readOnly=true:直接返回缓存中的对象实例(速度快),但可能被其他线程修改,存在线程安全问题,适合不常修改的常量数据。
  • readOnly=false(默认/推荐):返回缓存对象的反序列化副本(新对象),每个会话拿到的是独立的拷贝,线程安全。缺点:需要对象实现 Serializable,且序列化/反序列化有性能开销。

二级缓存 vs Redis/外部缓存

MyBatis 自带的二级缓存是本地缓存(存储在应用的内存中),对于分布式应用:

  • 问题:如果你的应用部署在多个服务器上,服务器A的缓存和服务器B的缓存是不共享的。
  • 解决方案:实现 org.apache.ibatis.cache.Cache 接口,将数据存入 Redis 等集中式缓存,MyBatis 官方提供了 Redis 和 Ehcache 的集成方案。
特性 二级缓存
共享范围 跨 SqlSession(同一实例的多个会话)
生命周期 与应用实例共存亡(或直到缓存被刷新)
触发条件 sqlSession.close()sqlSession.commit()(查询结果才会写入)
刷新条件 同Mapper下的增删改(insert/update/delete)
主要风险 多表关联时的脏数据、分布式不共享、内存占用
适用场景 读多写少数据一致性要求不高(如查字典数据、分类信息)

一句话概括:MyBatis二级缓存本质上是一个跨会话、按Mapper命名空间隔离的本地缓存,它通过“读写分离”和“增删改即失效”的简单机制,在单机应用里提升性能,但在分布式或多表关联复杂场景下,需要谨慎使用或替换为外部缓存。

抱歉,评论功能暂时关闭!