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缓存说明
用两个独立会话区分一级与二级缓存
在专用测试数据和已知事务条件下,可以设计这样的验证顺序:
- 会话A执行同一参数查询,然后按约定提交并关闭,使缓存进入可见阶段。
- 新建会话B执行相同查询,结合数据源SQL观测与缓存统计检查是否复用,而非在A内重复查询误认一级缓存为二级缓存。
- 通过对应namespace的写语句更新测试数据并提交,再新建会话C查询,确认缓存已失效。
- 再考虑另一namespace写入同表、外部SQL写入或另一应用实例更新的场景。
这里给的是验证方法,不是已运行结果。原日志中的 Cache Hit Ratio ... 0.0 恰好不能当作命中证据;应对照具体查询时序,而不是只看日志含有Cache字样。
一致性比命中率更重要
二级缓存通常跟随相应SqlSessionFactory与namespace,不会自动变成跨节点一致缓存。其他namespace或外部服务写入的数据,也不必然触发当前区域失效。
先确认数据更新频率、允许的陈旧程度和失效边界,再决定是否启用。不要因为两行配置很简单,就让需要强一致的数据承担未设计的缓存风险。