高 进阶
MyBatis一二级缓存#
一句话答案#
一级缓存 SqlSession 级别默认开启,二级缓存 Mapper 级别需手动开启,分布式环境建议关闭或用 Redis 替代。
核心要点
MyBatis 的核心功能:
- SQL 与代码分离:SQL 写在 XML 或注解中,Java 代码只调用接口方法
- 动态 SQL:
<if>,<foreach>,<choose>等标签,根据条件拼接 SQL - 结果映射(ResultMap):将 ResultSet 自动映射为 Java 对象(支持复杂嵌套关系)
- 一级缓存(SqlSession)/ 二级缓存(Mapper)
MyBatis 的 Mapper 接口代理实现:
@Mapper接口没有实现类,由 MyBatis 通过 JDK 动态代理生成代理对象- 调用 Mapper 方法 → 代理对象拦截 → 根据方法名和 namespace 找到对应 SQL → 执行
MyBatis 的插件机制(Interceptor/切面):
MyBatis 允许在以下四个核心对象的指定方法上插入拦截器:
Executor → update/query/commit/rollback(执行层)
StatementHandler → prepare/query/update(SQL 处理层)
ParameterHandler → setParameters(参数处理)
ResultSetHandler → handleResultSets(结果处理)java典型插件应用:
// 分页插件(PageHelper/MyBatis-Plus):拦截 Executor.query(),在 SQL 后追加 LIMIT
// 数据权限:拦截 query,在 WHERE 子句追加权限过滤条件(如 AND dept_id = :deptId)
// SQL 日志打印:拦截 StatementHandler,打印完整的带参数 SQL
// 数据脱敏:拦截 ResultSetHandler,对返回结果的敏感字段做脱敏处理
@Intercepts({@Signature(type=Executor.class, method="query", args={...})})
public class MyPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置处理
Object result = invocation.proceed(); // 执行原方法
// 后置处理
return result;
}
}java面试回答(2分钟版)
MyBatis 的缓存分两级。一级缓存是 SqlSession 级别的,默认开启,同一个 SqlSession 内相同的查询只会执行一次 SQL,后续直接从缓存取结果。一级缓存在执行 update/insert/delete 操作、手动调用 clearCache 或 SqlSession 关闭时失效。但在 Spring 中每次请求通常会创建新的 SqlSession,所以一级缓存的实际命中率并不高。二级缓存是 Mapper 级别的,需要在 XML 中配置 cache 标签手动开启,它可以跨 SqlSession 共享数据,但只在同一个 namespace 内有效。二级缓存的最大问题是多表联查时容易产生脏数据,因为关联表的更新不会触发当前 namespace 缓存失效。所以生产环境通常建议关闭 MyBatis 二级缓存,改用 Redis 做分布式缓存,既能跨服务共享又能精确控制失效策略。
追问与易错
追问方向:
- “一级缓存什么时候失效?”→ 执行 update/insert/delete 操作后自动清空、手动调用 sqlSession.clearCache()、SqlSession 关闭或提交时失效;Spring 中每次请求通常新建 SqlSession,实际命中率不高
- “二级缓存的脏数据问题?”→ 二级缓存按 namespace 隔离,多表联查时关联表的更新不会触发当前 namespace 缓存失效,导致查到过时数据;可用 cache-ref 引用其他 namespace 但会降低缓存效果
- “生产推荐开启二级缓存吗?”→ 通常不推荐,因为脏数据风险高、只是 JVM 级别缓存不支持分布式;生产环境建议关闭二级缓存,用 Redis 做分布式缓存精确控制失效策略
易错点:
- ❌ MyBatis 缓存等于 Redis 缓存——是 JVM 级别的
- ❌ 二级缓存跨 namespace 生效——只在同一 namespace