欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MyBatis拦截器

程序员文章站 2022-06-13 20:49:29
...

拦截器的作用

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

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed),MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。
ParameterHandler (getParameterObject, setParameters),负责对用户传递的参数转换成JDBC Statement 所需要的参数。
ResultSetHandler (handleResultSets, handleOutputParameters),负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
StatementHandler (prepare, parameterize, batch, update, query),封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。

如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为,
因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

实际的应用:在很多业务场景下我们需要去拦截sql,达到不入侵原有代码业务处理一些东西,比如:分页操作,数据权限过滤操作,SQL执行时间性能监控等等,这里我们就可以用到Mybatis的拦截器Interceptor
原理解析:https://blog.csdn.net/weixin_39494923/article/details/91534658
MyBatis拦截器 MyBatis的执行流程图。

MyBatis拦截器MyBatis的架构图

实现一个简单的拦截器

定义 Mybatis 拦截器只需要实现 Interceptor 类, 并通过注解设置需要拦截的对象即可:
type=>mybatis 的四大对象:[ParameterHandler],[ResultSetHandler],[StatementHandler],[Executor].
method => 四大对象中需要拦截的方法, 具体可以直接看源码. 例如 Executor 类的 update 方法就经常用到.
args=> 拦截方法所需要的参数, 如果不清楚的话也可以直接看源码. 例如 update 方法需要 MappedStatement 类型和 Object 类型的两个参数.
intercept(Invocation invocation)=> 此方法内写具体的拦截逻辑.
plugin()=> 没什么特殊要求的话直接写成如下形式即可, 意思是把定义的插件注册进插件链里面 (越晚注册越先执行).

需求:定义拦截器类 实现显示sql的输出量仅为1条,拦截到执行的sql再对该sql进行改写
自定义一个拦截器:

package com.wx.currencymapper.interceptor;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.util.Properties;
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class,Integer.class })})
@Component
public class SqlStatusInterceptor  implements Interceptor {
    /**拦截sql,实现显示sql的输出量仅为1条,思路:拦截到执行的sql再对该sql进行改写*/
    private static final Logger log=LoggerFactory.getLogger(SqlStatusInterceptor.class);
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        /**具体的拦截逻辑*/
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        //获取sql
        String sql= String.valueOf(metaStatementHandler.getValue("delegate.boundSql.sql"));
        //添加limit条件
        sql="select * from (" + sql + ") as temp limit 1";
        //重新设置sql
        metaStatementHandler.setValue("delegate.boundSql.sql",sql);
        return invocation.proceed();
    }

    /**
     * 把拦截器插件注册进插件链里面,越晚注册越先执行
     * @param o
     * @return
     */
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {
        String dialect = properties.getProperty("dialect");
        log.info("mybatis intercept dialect:{}", dialect);
    }
}

不使用拦截器查询:
MyBatis拦截器
使用拦截器查询:
MyBatis拦截器
再来一个需求:自定义拦截器实现在新增/更新操作时动态为sql注入creationDate时间字段

先自定义一个注解:

package com.wx.currencymapper.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定义拦截器时运行时注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface CreateTime {
    String value() default "";
}
其次编写拦截器,注意拦截的方法

package com.wx.currencymapper.interceptor;

import com.wx.currencymapper.annotation.CreateTime;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.Properties;

@Intercepts({ @Signature(type = Executor.class, method = “update”, args = { MappedStatement.class,Object.class })})
@Component
public class SqlInsertInterceptor implements Interceptor {
private static final Logger log=LoggerFactory.getLogger(SqlInsertInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {

    MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
    // 获取 SQL 命令
    SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
    log.info("获取到的sql命令为:{}",sqlCommandType);
    // 获取参数
    Object parameter = invocation.getArgs()[1];

    if (parameter != null) {
        // 获取成员变量
        Field[] declaredFields = parameter.getClass().getDeclaredFields();

        for (Field field : declaredFields) {
            if (field.getAnnotation(CreateTime.class) != null) {
                if (SqlCommandType.INSERT.equals(sqlCommandType)) { // insert 语句插入 createTime
                    field.setAccessible(true);
                    if (field.get(parameter) == null) {
                        field.set(parameter, new Date());
                    }
                }
            }

           /* if (field.getAnnotation(UpdateTime.class) != null) { // insert 或 update 语句插入 updateTime
                if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
                    field.setAccessible(true);
                    if (field.get(parameter) == null) {
                        field.set(parameter, new Date());
                    }
                }
            }*/
        }
    }
    return invocation.proceed();
}

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

@Override
public void setProperties(Properties properties) {

}

}
SQL的编写:

<insert id="insertOrders" parameterType="com.wx.currencymapper.domain.Orders">
        INSERT INTO
        orders(user_id,number,createtime,note)
        VALUES(1,#{number},#{createtime},#{note})
    </insert>

测试:

@RequestMapping("/insertOrders")
    @ResponseBody
    public void insertOrder(Orders orders){
        orders.setNumber("34");
        orders.setNote("啊哈哈啊哈");
        ordersService.insertOrders(orders);
    }

成功添加:
MyBatis拦截器