mybatis 拦截器(保姆级教程)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在现代 Java 开发中,MyBatis 作为一款轻量级的 ORM 框架,因其灵活的 SQL 写法和极简的设计理念,被广泛应用于企业级项目开发。然而,随着业务复杂度的提升,开发者常常需要在 SQL 执行的“关键节点”插入自定义逻辑。这时,mybatis 拦截器便成为了一个强大的工具。它允许开发者在不修改 MyBatis 核心代码的情况下,通过 AOP(面向切面编程)的方式,对 SQL 的生成、执行、结果处理等环节进行扩展。本文将从零开始,逐步解析 mybatis 拦截器的核心原理与实战技巧,帮助读者掌握这一进阶技术。


一、拦截器是什么?如何理解其工作原理?

1. 拦截器的核心概念

mybatis 拦截器(Interceptor)是 MyBatis 提供的一种插件机制,通过实现 org.apache.ibatis.plugin.Interceptor 接口,开发者可以在 SQL 执行的各个阶段插入自定义逻辑。例如:

  • 在 SQL 生成阶段,修改动态 SQL 的内容
  • 在 SQL 执行前记录日志
  • 在结果返回后进行数据过滤

可以将拦截器想象为一个“交通警察”:它站在 SQL 的“执行路口”,对每一条 SQL 进行检查、修改或记录。

2. 拦截器的工作流程

MyBatis 拦截器通过动态代理技术实现,其核心流程如下:

  1. 目标对象:拦截器需要拦截的对象(如 ExecutorStatementHandler 等);
  2. 方法拦截:在目标对象的方法调用前后插入自定义逻辑;
  3. 责任链模式:多个拦截器可以形成链式调用,依次处理请求。

举个生活化的例子:假设你寄送快递时,需要经过安检(拦截器1)、称重(拦截器2)、贴单(拦截器3)等多个环节,每个环节都是一个独立的拦截器,共同完成最终的快递处理流程。


二、拦截器的实现步骤详解

1. 实现 Interceptor 接口

首先,开发者需要创建一个类继承 Interceptor 接口,并重写两个核心方法:

public class MyInterceptor implements Interceptor {  
    @Override  
    public Object intercept(Invocation invocation) throws Throwable {  
        // 在此编写拦截逻辑  
        return invocation.proceed(); // 继续执行后续流程  
    }  

    @Override  
    public Object plugin(Object target) {  
        return Plugin.wrap(target, this); // 包装目标对象  
    }  
}  

2. 配置拦截器

在 MyBatis 的配置文件(mybatis-config.xml)或 Spring Boot 的配置类中注册拦截器:

<plugins>  
    <plugin interceptor="com.example.MyInterceptor">  
        <!-- 可配置拦截器参数 -->  
        <property name="logLevel" value="DEBUG"/>  
    </plugin>  
</plugins>  

3. 拦截器的生命周期

拦截器的 intercept 方法会在每次目标对象的方法被调用时触发。例如,当执行 SqlSession.selectList() 时,会依次经过 ExecutorStatementHandler 等对象的方法调用,触发对应的拦截器逻辑。


三、拦截器的常见应用场景与代码示例

1. 场景一:SQL 日志增强

通过拦截器记录 SQL 的执行时间、参数、返回结果等信息,便于调试和性能优化。

实现代码

public class LoggingInterceptor implements Interceptor {  
    @Override  
    public Object intercept(Invocation invocation) throws Throwable {  
        long startTime = System.currentTimeMillis();  
        // 获取目标对象类型(如 Executor)  
        Class<?> targetType = invocation.getTarget().getClass();  
        // 执行原生方法  
        Object result = invocation.proceed();  
        long endTime = System.currentTimeMillis();  
        System.out.println("[" + targetType.getSimpleName() + "]" +  
            "方法 " + invocation.getMethod().getName() +  
            " 耗时:" + (endTime - startTime) + " ms");  
        return result;  
    }  
}  

效果

执行 SQL 后,控制台会输出类似以下信息:

[SimpleExecutor] 方法 query 耗时:15 ms

2. 场景二:动态参数增强

在 SQL 执行前,动态修改查询参数(如自动填充时间戳)。

实现代码

public class AutoFillInterceptor implements Interceptor {  
    @Override  
    public Object intercept(Invocation invocation) throws Throwable {  
        // 获取当前调用对象(可能是 StatementHandler)  
        Object target = invocation.getTarget();  
        if (target instanceof StatementHandler) {  
            StatementHandler handler = (StatementHandler) target;  
            // 获取绑定参数  
            MetaObject metaObject = SystemMetaObject.forObject(handler);  
            Object parameterObject = metaObject.getValue("delegate.boundSql.parameterObject");  
            if (parameterObject instanceof MyEntity) {  
                MyEntity entity = (MyEntity) parameterObject;  
                entity.setCreateTime(new Date()); // 自动填充时间戳  
            }  
        }  
        return invocation.proceed();  
    }  
}  

关键点解释

  • 通过 MetaObject 访问 MyBatis 内部对象的属性;
  • 需要判断目标对象类型,避免对非目标对象进行操作。

3. 场景三:性能监控与限流

通过拦截器统计 SQL 的执行频率,实现简单的限流逻辑。

实现代码

public class RateLimitInterceptor implements Interceptor {  
    private static final Map<String, Long> requestCount = new HashMap<>();  
    @Override  
    public Object intercept(Invocation invocation) throws Throwable {  
        String sql = getSqlFromInvocation(invocation);  
        long currentTime = System.currentTimeMillis();  
        if (requestCount.containsKey(sql)) {  
            long lastTime = requestCount.get(sql);  
            if (currentTime - lastTime < 1000) { // 同一 SQL 1秒内只能执行一次  
                throw new RuntimeException("SQL rate limit exceeded!");  
            }  
        }  
        requestCount.put(sql, currentTime);  
        return invocation.proceed();  
    }  

    private String getSqlFromInvocation(Invocation invocation) {  
        // 通过反射获取 SQL 内容(此处简化逻辑)  
        return "SELECT * FROM users";  
    }  
}  

四、拦截器的高级技巧与注意事项

1. 拦截器的执行顺序

多个拦截器的执行顺序由注册顺序决定。例如,在 mybatis-config.xml 中先注册 A 再注册 B,则 A 的拦截器逻辑会先于 B 执行。

2. 避免无限递归调用

intercept 方法中,若对同一个拦截器多次调用 invocation.proceed(),可能导致栈溢出。需确保每个拦截器仅调用一次 proceed()

3. 目标对象的类型判断

由于拦截器可能作用于 ExecutorStatementHandler 等不同对象,需在代码中通过 instanceof 判断目标类型,避免 ClassCastException


五、总结与展望

通过本文的讲解,我们掌握了 mybatis 拦截器的基本原理、实现步骤和典型应用场景。拦截器作为 MyBatis 的“扩展钩子”,能够帮助开发者在不侵入框架核心的情况下,实现灵活的业务逻辑扩展。

未来,随着项目复杂度的提升,开发者可以尝试将拦截器与日志框架(如 SLF4J)、缓存机制(如 Redis)结合,进一步提升系统的可维护性和性能。例如:

  • 动态 SQL 缓存:根据 SQL 特征自动缓存查询结果;
  • 安全审计:拦截敏感操作并记录审计日志。

掌握 mybatis 拦截器不仅是技术进阶的必经之路,更是构建健壮、可扩展系统的重要工具。希望本文能为你打开这一技术领域的“大门”。

最新发布