JAVA面试宝典 - 《MyBatis 进阶:插件开发与二级缓存》

发布于:2025-07-15 ⋅ 阅读:(14) ⋅ 点赞:(0)

🚀《MyBatis 进阶:插件开发与二级缓存》

🎯 目标读者
已掌握 MyBatis 基础映射与 Mapper 使用,
想深入了解 MyBatis 插件机制与二级缓存原理及实践。


一、开场引导:MyBatis 不只是 ORM 工具

MyBatis 以 灵活可插拔轻量级 著称,但你是否真正了解它底层“魔法”?

  • 插件 能干预 SQL 执行流程:日志、权限、监控……
  • 二级缓存SqlSession 提升查询性能:内存、分布式都可扩展!

问题抛出:

  • 如何开发一个统计 SQL 执行耗时的插件?
  • 二级缓存如何从内存跑到 Redis?

二、MyBatis 插件机制详解

2.1 插件本质是“执行栈拦截器”

类比:Spring AOP 是“分层切面”,MyBatis 插件更像给执行栈每一站“排队拦车”——
每到关键节点,拦下并加入自定义逻辑,再放行。

可拦截的四大方法(对应核心四大处理器):

处理器 可拦截方法
Executor query()update()
ParameterHandler setParameters()
ResultSetHandler handleResultSets()
StatementHandler prepare()parameterize()batch()

2.2 插件开发实战:记录 SQL 执行耗时

步骤一:实现 Interceptor
@Intercepts({
  @Signature(type = Executor.class,
             method = "query",
             args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SqlTimingInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.nanoTime();
        Object result = invocation.proceed();
        long elapsedMs = (System.nanoTime() - start) / 1_000_000;
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        if (elapsedMs > 100) {
            System.err.printf("[Slow SQL] %s took %d ms%n", ms.getId(), elapsedMs);
        }
        return result;
    }
    @Override public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override public void setProperties(Properties props) { }
}
步骤二:注册插件
<plugins>
  <plugin interceptor="com.example.mybatis.SqlTimingInterceptor"/>
</plugins>

2.3 插件原理揭秘

  1. Plugin.wrap(target, interceptor)
  • 如果目标类型匹配 @Signature,则用 JDK 动态代理或 CGLIB 生成代理对象。
  1. 链式拦截
  • 多个插件按配置顺序“嵌套”,先注册的最外层,最后执行的最内层。
  1. 执行流程
ExecutorImpl.query() 
  → interceptor1.intercept() 
    → interceptor2.intercept() 
      → 实际 query 方法

三、MyBatis 二级缓存机制

3.1 一级缓存 vs 二级缓存

缓存类型 范围 生命周期 默认开启 可配置性
一级缓存 单个 SqlSession 会话内 不可关闭
二级缓存 Mapper 映射空间 SqlSession 可开启/禁用
  • 一级缓存:类似图书馆阅览室,只在“当天”可重复借阅。

  • 二级缓存:像书架借书,可以跨会话“长期”缓存(甚至跨进程)。

3.2 启用二级缓存的步骤

  1. Mapper 接口/映射文件 加上注解或 XML:
@CacheNamespace(implementation=RedisCache.class, eviction=FifoCache.class, flushInterval=60000)
public interface UserMapper { ... }

  1. 实体类实现 Serializable
  2. 查询方法返回可序列化对象,不能是流或 ResultHandler

写操作(insert/update/delete)会清空对应 namespace 下的二级缓存。

3.3 缓存失效与扩展

  • Session 关闭:一级缓存清空
  • 写操作:清空二级缓存
  • 命名空间隔离:不同 Mapper 之间不共享
  • 自定义缓存:可实现 org.apache.ibatis.cache.Cache 接口,用 Redis、Guava 等

四、实战案例:自定义 Cache 接入 Redis

4.1 实现 Cache 接口

public class RedisCache implements Cache {
    private final String id;
    private RedisTemplate<String,Object> redis;
    public RedisCache(String id) { this.id = id; }
    @Override public String getId() { return id; }
    @Override public void putObject(Object key, Object value) {
        redis.opsForHash().put(id, key.toString(), value);
    }
    @Override public Object getObject(Object key) {
        return redis.opsForHash().get(id, key.toString());
    }
    @Override public Object removeObject(Object key) {
        return redis.opsForHash().delete(id, key.toString());
    }
    @Override public void clear() {
        redis.delete(id);
    }
    @Override public int getSize() {
        return redis.opsForHash().size(id).intValue();
    }
}

4.2 注册自定义缓存

<cache type="com.example.mybatis.RedisCache"/>

发布到分布式集群时,所有节点都可共享同一 Redis 二级缓存。

五、常见问题与性能建议

问题 建议
插件过多影响性能 精简插件链,按需拦截;生产环境剔除非必要插件
二级缓存命中率低 减少写操作频率;针对热点数据单独设计缓存清理策略
数据一致性难保证 配合消息队列或 Redis 发布/订阅实现失效通知
统计所有 SQL Executor.query()update() 中同时拦截

六、总结

  • 插件机制:通过 Interceptor 拦截执行栈,灵活扩展 SQL 监控、审计、权限等功能。
  • 二级缓存:跨会话缓存结果,默认内存实现,可自定义 Redis、Guava 等持久化方式。
  • 进阶能力:掌握插件与缓存设计,意味着你已步入 MyBatis 的“进阶圈”,能让 ORM 不止“映射”,还能“加速”与“智能化”!

📚 推荐阅读

  • MyBatis 官方文档:插件开发 & 缓存章节
  • MyBatis 源码深度解析
  • Redis 官方文档:Hash 结构与内存优化

网站公告

今日签到

点亮在社区的每一天
去签到