MyBatis中如何实现自定义插件

比如可以使用自定义插件来添加分页参数,或统计sql的执行时间,实现方式为:

  1. 创建插件类并实现Interceptor接口
  • intercept():编写拦截逻辑,在目标方法前后执行自定义操作。
  • plugin():返回目标对象的代理对象。
  • setProperties():接收配置文件中的参数(可选)。
java 复制代码
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Connection;
import java.util.Properties;

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

    private String dialect = "mysql"; // 默认数据库类型

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(handler);
        
        // 获取原始SQL和分页参数
        BoundSql boundSql = handler.getBoundSql();
        String originalSql = boundSql.getSql();
        Object parameterObject = boundSql.getParameterObject();

        // 检查是否包含分页参数
        if (parameterObject instanceof Page<?>) {
            Page<?> page = (Page<?>) parameterObject;
            
            // 修改SQL添加分页
            String paginatedSql = buildPaginationSql(originalSql, page);
            metaObject.setValue("delegate.boundSql.sql", paginatedSql);
            
            // 设置分页参数(LIMIT和OFFSET)
            metaObject.setValue("delegate.boundSql.parameterObject", page.getData());
        }
        
        return invocation.proceed();
    }

    // 构建分页SQL
    private String buildPaginationSql(String sql, Page<?> page) {
        int offset = (page.getPageNum() - 1) * page.getPageSize();
        if ("mysql".equals(dialect)) {
            return sql + " LIMIT " + page.getPageSize() + " OFFSET " + offset;
        } else if ("oracle".equals(dialect)) {
            // Oracle分页逻辑(使用ROWNUM)
            return "SELECT * FROM ( SELECT tmp.*, ROWNUM rn FROM (" + sql + ") tmp ) WHERE rn > " 
                   + offset + " AND rn <= " + (offset + page.getPageSize());
        }
        return sql;
    }

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

    @Override
    public void setProperties(Properties properties) {
        // 提取参数
        this.dialect = properties.getProperty("dialect", "mysql");
    }
}
  1. 使用@Intercepts注解指定拦截目标

@Signature注解定义要拦截的类、方法及参数类型。例如:

  • type:拦截的接口(如ExecutorStatementHandler)。
  • method:方法名。
  • args:方法的参数类型数组。
  1. 注册插件到MyBatis配置
xml 复制代码
<plugins>
    <plugin interceptor="com.example.PaginationPlugin">
        <property name="dialect" value="mysql"/>
    </plugin>
</plugins>
  1. mapper层使用示例
less 复制代码
public interface UserMapper {
    @Pageable
    List<User> selectByCondition(@Param("page") Page<User> page, @Param("name") String name);
}

注意事项:

  • 仅支持拦截四大接口ExecutorStatementHandlerParameterHandlerResultSetHandler
  • 方法签名匹配 :需确保@Signature中的参数类型与目标方法完全一致。
  • 性能影响:过度使用插件可能影响性能,建议仅在必要时使用。
相关推荐
yuzhiboyouye8 小时前
web前端英语面试
前端·面试·状态模式
我叫黑大帅9 小时前
为什么需要 @types/react?解决“无法找到模块 react 的声明文件”报错
前端·javascript·面试
折哥的程序人生 · 物流技术专研11 小时前
Java面试85题图解版(一):基础核心篇
java·开发语言·后端·面试
哆啦A梦158812 小时前
20, Springboot3+vue3实现前台轮播图和详情页的设计
javascript·数据库·spring boot·mybatis·vue3
Moment12 小时前
面试官:如果产品经理给你多个需求,怎么让AI去完成❓❓❓
前端·后端·面试
Lkstar12 小时前
面试官让我手写 Promise.all / Promise.race / Promise.allSettled,我直接水灵灵地写出来了
javascript·面试
逻辑驱动的ken13 小时前
Java高频考点场景题24
java·开发语言·面试·职场和发展·求职招聘
Fox爱分享13 小时前
字节二面:10亿数据毫秒级查手机尾号后4位,答不出“异构索引”直接挂?
java·后端·面试
WaywardOne14 小时前
Flutter面试事件队列,微任务队列以及事件循环相关问题及回答
flutter·面试
折哥的程序人生 · 物流技术专研14 小时前
《Java面试85题图解版(二)》进阶深化上篇:并发编程 + JVM
java·开发语言·后端·面试