MyBatis二级缓存需要区分全局开关、Mapper namespace配置和事务边界。把 cache-enabled 设为true,并不代表所有查询都已经启用二级缓存;看到一行缓存日志,也不等于这次查询命中。

全局允许与namespace开启是两层

使用对应MyBatis Spring Boot Starter时,可显式写:

mybatis.configuration.cache-enabled=true

MyBatis的 cacheEnabled 默认值本来就是true,它控制是否允许使用已配置的缓存;namespace通常仍需配置cache。不要把这一行描述为从全局默认false开启的必要步骤。MyBatis配置说明

Mapper XML中,在对应namespace内加入:

<mapper namespace="example.mapper.UserMapper">
  <cache />
  <!-- resultMap、select等映射放在同一mapper中 -->
</mapper>

这是配置位置示意,省略了项目自己的语句;缓存是否参与还取决于select的useCache等设置。注解声明语句与XML混用时,也要确认缓存区域关联,不能只看同名接口就假定已经共享。

Serializable错误来自具体缓存语义

2020年的旧项目配置后遇到对象序列化问题,实现Serializable后可以继续运行。默认 <cache/> 的readOnly为false,读写缓存通过序列化复制对象;因此缓存结果及相关对象图要能序列化。

import java.io.Serializable;

public final class UserView implements Serializable {
    private static final long serialVersionUID = 1L;
    private final long id;
    private final String name;

    public UserView(long id, String name) {
        this.id = id;
        this.name = name;
    }

    public long getId() { return id; }
    public String getName() { return name; }
}

这是可序列化结果对象示例,不包含Mapper与数据库工程。自定义缓存实现可能有不同要求,不能说“一切二级缓存都必须同样序列化”。也不应为了绕过异常随意改为readOnly=true:那会返回共享实例,调用方修改可能污染其他请求。MyBatis缓存说明

用两个独立会话区分一级与二级缓存

在专用测试数据和已知事务条件下,可以设计这样的验证顺序:

  1. 会话A执行同一参数查询,然后按约定提交并关闭,使缓存进入可见阶段。
  2. 新建会话B执行相同查询,结合数据源SQL观测与缓存统计检查是否复用,而非在A内重复查询误认一级缓存为二级缓存。
  3. 通过对应namespace的写语句更新测试数据并提交,再新建会话C查询,确认缓存已失效。
  4. 再考虑另一namespace写入同表、外部SQL写入或另一应用实例更新的场景。

这里给的是验证方法,不是已运行结果。原日志中的 Cache Hit Ratio ... 0.0 恰好不能当作命中证据;应对照具体查询时序,而不是只看日志含有Cache字样。

一致性比命中率更重要

二级缓存通常跟随相应SqlSessionFactory与namespace,不会自动变成跨节点一致缓存。其他namespace或外部服务写入的数据,也不必然触发当前区域失效。

先确认数据更新频率、允许的陈旧程度和失效边界,再决定是否启用。不要因为两行配置很简单,就让需要强一致的数据承担未设计的缓存风险。