MyBatis 插件开发的完整详细例子

MyBatis 插件开发的完整详细例子

在这里插入图片描述

MyBatis 插件(Interceptor)允许开发者在已映射语句执行过程中的某一点进行拦截调用,从而实现自定义逻辑。以下是一个完整的 MyBatis 插件开发示例,涵盖所有使用场景,并附有详细注释和总结。


1. MyBatis 插件基础

MyBatis 允许拦截以下接口的方法:

  • Executorupdate, query, flushStatements, commit, rollback, getTransaction, close, isClosed
  • ParameterHandlergetParameterObject, setParameters
  • ResultSetHandlerhandleResultSets, handleCursorResultSets, handleOutputParameters
  • StatementHandlerprepare, parameterize, batch, update, query

2. 插件开发示例

2.1. 自定义插件类

创建一个自定义插件类 MyPlugin,该插件将拦截 Executorquery 方法和 StatementHandlerprepare 方法。

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.statement.StatementHandler;

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

@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取被拦截方法的参数
        Object[] args = invocation.getArgs();

        // 拦截 Executor.query 方法
        if (invocation.getTarget() instanceof Executor) {
            MappedStatement ms = (MappedStatement) args[0];
            Object parameter = args[1];
            RowBounds rowBounds = (RowBounds) args[2];
            ResultHandler resultHandler = (ResultHandler) args[3];

            BoundSql boundSql = ms.getBoundSql(parameter);
            String sql = boundSql.getSql();
            System.out.println("Executing SQL: " + sql);

            // 执行原方法
            return invocation.proceed();
        }

        // 拦截 StatementHandler.prepare 方法
        if (invocation.getTarget() instanceof StatementHandler) {
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            Connection connection = (Connection) args[0];
            Integer integer = (Integer) args[1];

            // 获取原始 SQL
            BoundSql boundSql = statementHandler.getBoundSql();
            String originalSql = boundSql.getSql();
            System.out.println("Original SQL: " + originalSql);

            // 修改 SQL(例如添加注释)
            String newSql = "/* MyPlugin */ " + originalSql;
            BoundSql newBoundSql = new BoundSql(
                boundSql.getMappedStatement().getConfiguration(),
                newSql,
                boundSql.getParameterMappings(),
                boundSql.getParameterObject()
            );
            MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
            metaObject.setValue("delegate.boundSql", newBoundSql);

            // 执行原方法
            return invocation.proceed();
        }

        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {
        // 设置插件属性(可选)
    }
}
2.2. 配置插件

mybatis-config.xml 中配置自定义插件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 配置插件 -->
    <plugins>
        <plugin interceptor="com.example.plugin.MyPlugin">
            <!-- 可以设置插件属性 -->
            <!-- <property name="propertyName" value="propertyValue"/> -->
        </plugin>
    </plugins>

    <!-- 其他配置... -->
</configuration>
2.3. 测试插件

编写测试代码来验证插件的功能:

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.Reader;

public class PluginTest {
    public static void main(String[] args) throws Exception {
        // 读取 MyBatis 配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);

            // 执行查询操作,触发插件拦截
            User user = mapper.selectUserById(1L);
            System.out.println("User: " + user.getName());
        }
    }
}

3. 核心使用场景

3.1. 日志记录

Executorqueryupdate 方法中添加日志记录,便于调试和监控。

3.2. SQL 修改

StatementHandlerprepare 方法中修改 SQL 语句,例如添加统一的注释或进行性能优化。

3.3. 参数处理

ParameterHandlersetParameters 方法中对参数进行预处理,如加密、格式化等。

3.4. 结果集处理

ResultSetHandlerhandleResultSets 方法中对结果集进行后处理,如数据脱敏、缓存等。


4. 表格整理总结

场景拦截接口及方法具体应用示例代码片段
日志记录Executor.query, Executor.update在执行 SQL 前后记录日志System.out.println("Executing SQL: " + sql);
SQL 修改StatementHandler.prepare修改或优化 SQL 语句String newSql = "/* MyPlugin */ " + originalSql;
参数处理ParameterHandler.setParameters对参数进行预处理(如加密)preparedStatement.setString(1, encrypt(parameter));
结果集处理ResultSetHandler.handleResultSets对查询结果进行后处理(如脱敏)resultList.forEach(item -> item.setEmail(maskEmail(item.getEmail())));
性能监控Executor.query, Executor.update记录 SQL 执行时间long startTime = System.currentTimeMillis(); ... long endTime = ...
分页支持StatementHandler.parameterize动态添加分页参数preparedStatement.setInt(1, offset); preparedStatement.setInt(2, limit);

5. 最佳实践建议

  1. 理解底层行为:在重写方法时,需理解其底层行为,避免破坏 MyBatis 的核心功能。
  2. 谨慎修改 SQL:修改 SQL 时要确保语法正确,避免引入潜在的 SQL 注入风险。
  3. 合理使用属性:通过 setProperties 方法为插件设置属性,增强灵活性。
  4. 单元测试:编写单元测试验证插件功能,确保其在各种场景下的稳定性。
  5. 文档记录:详细记录插件的使用方法和注意事项,便于团队成员理解和维护。

通过以上示例和总结,可以全面掌握 MyBatis 插件的开发和应用场景。

MyBatis开发插件可以让你自定义拦截和修改其内部的工作流程,如拦截 SQL 执行前后的状态、改变参数或结果集等等。MyBatis 支持通过创建实现了 `org.apache.ibatis.plugin.Interceptor` 接口的类来自定义插件,并允许开发者对该框架的核心组件进行增强。 以下是关于如何为 MyBatis 创建插件的具体步骤以及注意事项: ### 一、理解插件机制 MyBatis 插件本质上是一种 AOP(面向切面编程)技术的应用,它能够在不修改目标对象代码的前提下对其方法调用过程中的某些阶段插入逻辑。MyBatis 内置了几种类型的拦截点 (Intercepts),主要包括: - **Executor**:负责管理映射语句执行的操作,包括缓存、事务控制等; - **StatementHandler**:处理 JDBC PreparedStatement 相关操作的地方; - **ParameterHandler** :负责将传入参数设置到 PreparedStatement 上; - **ResultSetHandler** : 解析查询返回的结果集并将它们转化为 JavaBean 或 Map 类型的对象集合。 你可以选择其中一个或者多个作为你要监控的目标接口来进行扩展。 ### 二、编写插件类 为了构建一个合法有效的插件,你需要做到两件事: 1. 继承 `Interceptor` 接口并实现其中唯一的一个抽象方法——即 `intercept(Invocation invocation)` 方法;此方法将会接收到所有符合条件的方法调用事件。 2. 使用 `@Intercepts` 和 `@Signature` 注解描述出哪些接口及其具体方法应该受到本插件的影响范围之内。(如果使用XML形式配置,则不需要注解) 举个简单例子说明如何创建这样一个最基础版本的日志打印插件: ```java package com.example.plugins; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import java.sql.Connection; import java.util.Properties; // 定义这个插件作用于 StatementHandler 的 prepare() 方法 @Intercepts({ @Signature( type=StatementHandler.class, method="prepare", args={Connection.class, Integer.class}) }) public class LoggingPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("Prepared statement is about to be executed..."); // 调用下一个链路节点继续向下传递请求 return invocation.proceed(); } @Override public void setProperties(Properties properties) {} } ``` 上面的例子展示了当任意地方触发了一个基于 `StatementHandler` 的 `prepare()` 方法时会先经过此处输出一句日志再往下走的标准流程。 > 注意事项: > >- 如果你在 `invocation.proceed();` 后边做了异常捕获并且未再次抛出去的话可能会导致后续真正需要做的业务逻辑得不到正确响应甚至直接失败掉!因此尽量避免在这里做过多复杂运算除非确定不会影响正常程序流。 >- 若想让某个特定条件下才生效可以在 `intercept()` 方法里增加判断逻辑来决定是否继续转发还是中途打断当前调用栈。 ### 三、注册插件 完成了上述内容之后还需要告诉 MyBatis 关于此插件的存在位置才能让它起效。有两种主流途径可供选用: #### 方式A - 通过 XML 文件声明 编辑你的主配置文件(`mybatis-config.xml`)并在 `<plugins>` 下新增一行引用刚才写的那个类全限定名即可: ```xml <configuration> <!-- ...其他配置项略 --> <plugins> <plugin interceptor="com.example.plugins.LoggingPlugin"/> </plugins> <!-- ...其余省略 --> </configuration> ``` #### 方式B - 采用自动扫描加载 对于现代 IDE (Eclipse/IntelliJ IDEA)而言可以直接把插件放置在一个合适的位置然后借助Spring容器提供的包扫描特性找到所有标记了相应注释 (@Component/@Service 等) 的 Bean 自动注入进去,不过这样做之前得确保已经导入了合适的 starter jar 并适当调整好工程结构才行哦~ 总之以上便是整个创建及部署过程中涉及的主要知识点啦~希望能帮到你!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱的叹息

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值