【MyBatis面试必问系列01】MyBatis插件实现与运行原理

一、插件实现方式

Mybatis插件又称拦截器。Mybatis是通过动态代理的方式实现拦截的。Mybatis仅可以编写针对Executor、StatementHandler、ParameterHandler、ResultSetHandler这4种接口的插件。

  1. 实现Mybatis的Interceptor接口并重写intercept()方法。

  2. 然后在给插件编写注解,指定要拦截哪一个接口的哪些方法。

例如,如果你想拦截 Executorupdate 方法,你需要在 @Intercepts 注解中包含一个 @Signature 注解,指定 type = Executor.class, method = "update"

  1. 在配置文件中(通常是 mybatis-config.xml)配置你编写的插件。

例如,<plugin interceptor="com.example.MyPlugin"/>

二、插件运行原理

Mybatis采用 责任链模式 ,通过 动态代理 组织多个插件,通过这些插件可以改变Mybatis的默认行为。Mybatis初始化的时候创建 InterceptorChain 里保存了所有的插件。

  1. 创建Executor、StatementHandler、ParameterHandler、ResultSetHandler对象的时候,就会根据编写的插件,对对象生成代理链。源代码如下图:

interceptorChain.pluginAll方法里通过 interceptor.plugin(target) 生成动态代理,循环生成代理链,源代码下图:

  1. 代理链的InvocationHandler中的invoke()方法中调用实现Interceptor接口插件的intercept()方法。源代码如下图:
  1. 调用 invocation.proceed() 或 method.invoke(target, args)将代理向内层推进,直到真正目标类。源代码如下图:

三、插件编写举例

编写一个 MyBatis 插件来添加 LIMIT 用于分页是一个相对复杂的过程。这里是一个基本的示例,说明如何创建这样一个插件。请注意,这个示例是基于 MyBatis 3 的假设。

首先,您需要创建一个插件类,该类实现了 Interceptor 接口。该插件会拦截 StatementHandlerprepare 方法。然后,您可以在执行查询之前修改 SQL 语句来添加 LIMIT 分页。

typescript 复制代码
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Connection;
import java.util.Properties;

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
public class PaginationInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取到当前的StatementHandler
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        // 通过反射获取到当前StatementHandler的delegate属性
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);

        // 分离代理对象链
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }

        // 分离最后一个代理对象的目标类
        while (metaStatementHandler.hasGetter("target")) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }

        // 获取当前StatementHandler的boundSql
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");

        // 获取到原始sql语句
        String sql = boundSql.getSql();

        // 修改sql,这里添加了简单的示例,实际情况可能需要更复杂的逻辑来判断和添加limit
        String limitSql = sql + " LIMIT " + /* 这里添加你的分页参数 */;

        // 重写要执行的sql
        metaStatementHandler.setValue("delegate.boundSql.sql", limitSql);

        // 将执行权交给下一个拦截器
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {
        // 这里可以接收到配置的属性
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}

这个插件的关键部分在于 intercept 方法。这里,我们获取了原始的 SQL 语句,并且在其末尾添加了 LIMIT 子句。实际情况下,您可能需要根据具体的分页参数动态构建这个 LIMIT 子句。

要使这个插件生效,您还需要在 MyBatis 的配置文件中进行配置:

xml 复制代码
<plugins>
    <plugin interceptor="com.example.mybatis.PaginationInterceptor"/>
</plugins>
相关推荐
金色的暴发户7 分钟前
苦尽甘来终有时,一路向阳待花开
前端·javascript·面试
软件测试曦曦8 分钟前
10:00开始面试,10:08就出来了,问的问题有点变态。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展
摆烂工程师12 分钟前
Grok3 API 已经全网上线了!并且 Grok3 API 可以免费使用
前端·后端·程序员
weixin_4708802621 分钟前
MYSQL——SQL语句到底怎么执行
数据库·mysql·面试
增强26 分钟前
腾讯云人脸核身服务端实现(一)
后端
Emma歌小白38 分钟前
DataFrame 是否不为空
后端
拉丁解牛说技术1 小时前
AI大模型 prompt 工程指南实战
后端·程序员·mcp
有来技术1 小时前
全栈架构后端攻坚:基于 youlai - boot(开源)、Spring Boot 3 与 Spring Security 6 实现企业级权限系统全功能实战手册
java·spring boot·后端
helloworld_工程师1 小时前
Spring AI应用:利用DeepSeek+嵌入模型+Milvus向量数据库实现检索增强生成--RAG应用(超详细)
java·后端
uhakadotcom1 小时前
使用Cloudflare Workers打造全栈应用:从静态网站到复杂后端
后端·面试·github