SQL拦截(二)InnerInterceptor

news/2024/11/6 6:39:58 标签: java

一、介绍

1、简介

InnerInterceptor 接口是 MyBatis-Plus 提供的一个拦截器接口,用于实现一些常用的 SQL 处理逻辑。

二、API

InnerInterceptor 接口继承自 MyBatis 的 Interceptor 接口,并添加了一些新的方法,用于处理 MyBatis-Plus 的特定功能。

java">package com.baomidou.mybatisplus.extension.plugins.inner;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

public interface InnerInterceptor {
    default boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        return true;
    }

    default void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    }

    default boolean willDoUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
        return true;
    }

    default void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
    }

    default void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
    }

    default void beforeGetBoundSql(StatementHandler sh) {
    }

    default void setProperties(Properties properties) {
    }
}

willDoQuery: 当执行查询操作时,MyBatis 会调用该方法,判断是否需要执行查询操作。默认返回true,表示继续执行查询操作,如果需要阻止查询操作,则可以在实现该方法时返回false。
beforeQuery: 在执行查询操作之前,MyBatis 会调用该方法。通过实现该方法,可以在查询之前进行一些必要的操作,例如设置数据范围、修改 SQL 等。
willDoUpdate: 当执行更新操作时,MyBatis 会调用该方法,判断是否需要执行更新操作。默认返回true,表示继续执行更新操作,如果需要阻止更新操作,则可以在实现该方法时返回false。
beforeUpdate: 在执行更新操作之前,MyBatis 会调用该方法。通过实现该方法,可以在更新之前进行一些必要的操作,例如设置更新时间、加密数据等。
beforePrepare: 在执行 SQL 之前,MyBatis 会调用该方法。通过实现该方法,可以在 SQL 执行之前进行一些必要的操作,例如设置事务隔离级别、设置查询超时时间等。
beforeGetBoundSql: 在获取 BoundSql 对象之前,MyBatis 会调用该方法。通过实现该方法,可以在获取 BoundSql 对象之前进行一些必要的操作,例如设置参数、修改 SQL 等。
setProperties: 设置拦截器属性。该方法在创建拦截器实例时调用,用于设置拦截器的属性。
pluginExecutor: 在创建 Executor 对象时调用,可以在这里对 Executor 对象进行包装或其他处理。

三、使用方法

1、自定义拦截类
implements Interceptor, InnerInterceptor 

(1)类头需要有@Intercepts注解;

(2)需要重写setProperties方法(方法体为空即可),否则会报错:

inherits unrelated defaults for setProperties(Properties) from types org.apache.ibatis.plugin.Interceptor and com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor
2、注册

和spring的拦截器org.springframework.web.servlet.HandlerInterceptor 需要注册一样,mybatis的拦截器也需要注册

java">import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.pluscache.demo.interceptor.SqlExplainInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {

    @Autowired
    private SqlExplainInterceptor sqlExplainInterceptor;

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(sqlExplainInterceptor);
        //interceptor.addInnerInterceptor(xxx);
        return interceptor;
    }
}

四、demo 

1、执行explain打印结果
@Data
public class ExplainResultVo {
    private String id;
    private String selectType;
    private String table;
    private String partitions;
    private String type;
    private String possibleKeys;
    private String key;
    private String keyLen;
    private String ref;
    private String rows;
    private String filtered;
    private String extra;
}
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.pluscache.demo.dto.ExplainResultVo;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;

@Slf4j
@Component
@Intercepts({
        @Signature(type = Executor.class, method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class SqlExplainInterceptor implements Interceptor, InnerInterceptor {

    @Override
    public void setProperties(Properties properties) {
        //无需改动
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("进入sql分析");
        try {
            Object target = invocation.getTarget();
            Object[] args = invocation.getArgs();
            if (target instanceof Executor) {
                final Executor executor = (Executor) target;
                Object parameter = args[1];
                log.info("参数,executor={},parameter={}", executor, parameter);
                boolean isUpdate = args.length == 2;
                MappedStatement ms = (MappedStatement) args[0];
                if (!isUpdate && ms.getSqlCommandType() == SqlCommandType.SELECT){
                    BoundSql boundSql;
                    if (args.length == 4) {
                        boundSql = ms.getBoundSql(parameter);
                    } else {
                        boundSql = (BoundSql) args[5];
                    }
                    String sql = getSql(boundSql, ms);
                    log.info("原来 sql 为 {}", sql);
                    this.handleExplain(executor, sql);
                    return invocation.proceed();
                }
            }
            return invocation.proceed();
        }catch (Exception e){
            return invocation.proceed();
        }
    }

    private String getSql(BoundSql boundSql, MappedStatement ms) {
        String sql = boundSql.getSql();
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
            for (ParameterMapping parameterMapping : parameterMappings) {
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = ms.getConfiguration().newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameter(value)));
                }
            }
        }
        return sql;
    }

    public String getParameter(Object parameter) {
        if (parameter instanceof String) {
            return "'" + parameter + "'";
        }
        return parameter.toString();
    }

    private void handleExplain(Executor executor, String sql) throws SQLException {
        Statement stmt = executor.getTransaction().getConnection().createStatement();
        stmt.execute("EXPLAIN " + sql + " ;");
        ResultSet rs = stmt.getResultSet();
        while (rs.next()) {
            ExplainResultVo explainResultVo = new ExplainResultVo();
            explainResultVo.setId(rs.getString("id"));
            explainResultVo.setSelectType(rs.getString("select_type"));
            explainResultVo.setTable(rs.getString("table"));
            explainResultVo.setPartitions(rs.getString("partitions"));
            explainResultVo.setType(rs.getString("type"));
            explainResultVo.setPossibleKeys(rs.getString("possible_keys"));
            explainResultVo.setKey(rs.getString("key"));
            explainResultVo.setKeyLen(rs.getString("key_len"));
            explainResultVo.setRef(rs.getString("ref"));
            String rows = rs.getString("rows");
            explainResultVo.setRows(rows);
            explainResultVo.setFiltered(rs.getString("filtered"));
            explainResultVo.setExtra(rs.getString("Extra"));
            boolean isSimple = "SIMPLE".equals(rs.getString("select_type"));
            int rowsInt = 0;
            if (StringUtils.isNotBlank(rows)) {
                try {
                    rowsInt = Integer.parseInt(rows);
                } catch (Exception e) {
                    rowsInt = 0;
                }
            }
            log.info("explain语句: {}", explainResultVo);
        }
    }
}


http://www.niftyadmin.cn/n/5740484.html

相关文章

241105_Pycharm切换jupyter环境(jupyter报缺失包)

241105_Pycharm切换jupyter环境&#xff08;jupyter报缺失包&#xff09; 使用jupyter notebook进行代码学习往往是一种效率很高的方法&#xff0c;我们可以随时查看变量的内容。 在使用conda管理虚拟环境时&#xff0c;往往我们在终端新建好虚拟环境并安装好所需要的包之后&…

需求和特性

需求和特性是软件开发中两个关键但不同的概念。了解它们之间的区别有助于更好地理解软件项目的目标和实现方式。 1. 定义 需求 需求是指用户或利益相关者对软件系统希望实现的功能、特性或行为的描述。需求可以是对系统要解决的问题、满足的业务目标或用户期望的具体描述。需…

知识课堂——高匿ip在不同业务中的重要作用

大家好&#xff01;今天我们来看看高匿ip在不同业务中都能起到什么样的重要作用。第一个会用到的地方就是网络数据采集&#xff0c;也被称为网络爬虫&#xff0c;在是许多企业和机构获取大量数据的重要手段。例如市场调研公司帮助企业制定市场策略就需要收集各个行业的产品价格…

BERT框架

文章目录 一、起源与背景二、模型架构三、预训练与微调四、模型特点与优势五、应用场景与限制 BERT框架&#xff0c;即Bidirectional Encoder Representations from Transformers框架&#xff0c;是一种为自然语言处理&#xff08;NLP&#xff09;领域设计的开源机器学习框架。…

HTMLCSS:3D 旋转卡片的炫酷动画

效果演示 这段代码是一个HTML和CSS的组合&#xff0c;用于创建一个具有3D效果的动画卡片。 HTML <div class"obj"><div class"objchild"><span class"inn6"><h3 class"text">我是谁&#xff1f;我在那<…

day03(单片机)GPIO

GPIO 灯如何才能亮 原理图 灯亮需要电流&#xff0c;产生电流需要电势差 单片机的电源3.3V —— LED - —— 1.0V&#xff08;单片机上比3.3V低的接口&#xff09; 二极管 二极管就是由一个PN结加上相应的电极引线及管壳封装而成的 特点&#xff1a;正向导通&#xff0c;反向截…

Python设计模式探究:单例模式实现及应用解析

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storm…

【多模态读论文系列】LLaMA-Adapter V2论文笔记

分享第二篇论文阅读笔记&#xff0c;欢迎指正&#xff0c;LLaMA-Adapter V2: Parameter-Efficient Visual Instruction Model LLaMA-Adapter V2: Parameter-Efficient Visual Instruction Model 论文&#xff1a;https://arxiv.org/abs/2304.15010 代码&#xff1a;https://…