拦截器介绍

MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:


1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
2. ParameterHandler (getParameterObject, setParameters)
3. ResultSetHandler (handleResultSets, handleOutputParameters)
4. StatementHandler (prepare, parameterize, batch, update, query)

我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。

总体概括为:

  1. 拦截执行器的方法
  2. 拦截参数的处理
  3. 拦截结果集的处理
  4. 拦截Sql语法构建的处理

拦截器使用

拦截sql并增强

import com.cq.controller.InterceptAnnotation;
import lombok.extern.log4j.Log4j2;
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.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Properties;

/**
 * sql拦截器,通过mybatis提供的Interceptor接口实现
 */
@Log4j2
@Component
//拦截StatementHandler类中参数类型为Statement的prepare方法(prepare=在预编译SQL前加入修改的逻辑)
//即拦截 Statement prepare(Connection var1, Integer var2) 方法
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MySqlInterceptor implements Interceptor {


    /**
     * 拦截sql
     *
     * @param invocation
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        // 通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性;:MetaObject是Mybatis提供的一个用于方便、
        // 优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                new DefaultReflectorFactory());

        // 先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        // id为执行的mapper方法的全路径名,如com.cq.UserMapper.insertUser, 便于后续使用反射
        String id = mappedStatement.getId();
        // sql语句类型 select、delete、insert、update
        String sqlCommandType = mappedStatement.getSqlCommandType().toString();
        // 数据库连接信息
//        Configuration configuration = mappedStatement.getConfiguration();
//        ComboPooledDataSource dataSource = (ComboPooledDataSource)configuration.getEnvironment().getDataSource();
//        dataSource.getJdbcUrl();

        BoundSql boundSql = statementHandler.getBoundSql();
        // 获取到原始sql语句
        String sql = boundSql.getSql().toLowerCase();
        log.info("SQL:{}", sql);


        // 增强sql
        // 通过反射,拦截方法上带有自定义@InterceptAnnotation注解的方法,并增强sql
        String mSql = sqlAnnotationEnhance(id, sqlCommandType, sql);
        // 直接增强sql
//      mSql = sql + " limit 2";

        //通过反射修改sql语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, mSql);
        log.info("增强后的SQL:{}", mSql); // 打印:增强后的SQL:select * from scenario_storage limit 2
        return invocation.proceed();
    }

    /**
     * 通过反射,拦截方法上带有自定义@InterceptAnnotation注解的方法,并增强sql
     * @param id 方法全路径
     * @param sqlCommandType sql类型
     * @param sql 所执行的sql语句
     */
    private String sqlAnnotationEnhance(String id, String sqlCommandType, String sql) throws ClassNotFoundException {
        // 通过类全路径获取Class对象
        Class<?> classType = Class.forName(id.substring(0, id.lastIndexOf(".")));
        // 获取当前所拦截的方法名称
        String mName = id.substring(id.lastIndexOf(".") + 1);
        // 遍历类中所有方法名称,并if匹配上当前所拦截的方法
        for (Method method : classType.getDeclaredMethods()) {
            if (mName.equals(method.getName())) {
                // 判断方法上是否带有自定义@InterceptAnnotation注解
                InterceptAnnotation interceptorAnnotation = method.getAnnotation(InterceptAnnotation.class);
                if (interceptorAnnotation.flag()) {
                    if ("SELECT".equals(sqlCommandType)) {
                        // 增强sql
                        return sql + " limit 2";
                        // select * from scenario_storage limit 2
                    }
                }
            }
        }
        return sql;
    }

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

    @Override
    public void setProperties(Properties properties) {
    }
}

 测试mapper接口

public interface IntercaperMapper {

    @InterceptAnnotation
    @Select("select * from scenario_storage")
    List<ScenarioStorageEntity> queryScenarioStorageAll();
}

实体类

import lombok.Data;

@Data
public class ScenarioStorageEntity {

    private int id;
    private String name;

}

自定义sql增强注解

/**
 * sql增强注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InterceptAnnotation {

    String value() default "";

    /** true增强、false忽略 */
    boolean flag() default true;

}

实践:用户只能看到自己相关的数据(通过mysql拦截器实现)

🍎原创不易,感觉对自己有用的话就❤️点赞👉收藏💬评论把。