MyBatis入坑之拦截器

6,591 阅读4分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

1. 拦截器注解

1.1 MyBatis自定义拦截器

  • 实现org.apache.ibatis.plugin.Interceptor接口。
  • 添加拦截器注解org.apache.ibatis.plugin.Intercepts
  • 配置文件中添加拦截器

1.2 在MyBatis中可被拦截的类型有四种(按照拦截顺序)

  • Executor:拦截执行器的方法。
  • ParameterHandler:拦截参数的处理。
  • ResultHandler:拦截结果集的处理。
  • StatementHandler:拦截Sql语法构建的处理,绝大部分我们是在这里设置我们的拦截器

(1)不同拦截类型执行顺序:

(2)多个插件拦截的顺序?

需要注意的是,因为拦截器Aa和拦截器Bb均是拦截的StatementHandler对象,所以拦截器B在此获取StatementHandler的时候,获取的是代理对象

(3)多个插件plugin()和intercept()方法的执行顺序

先执行每个插件的plugin方法,若是@Intercepts注解标明需要拦截该对象,那么生成类型对象的代理对象。(即使该插件需要拦截该类型对象,但是依旧会执行下一个插件的plugin方法)。知道执行完毕所有的plugin方法。在执行每个Intercept方法。

1.3 拦截器注解的作用:

自定义拦截器必须使用MyBatis提供的注解来声明我们要拦截的类型对象。

Mybatis插件都要有Intercepts注解来指定要拦截哪个对象哪个方法。我们知道,Plugin.wrap方法会返回四大接口对象的代理对象,会拦截所有的方法。在代理对象执行对应方法的时候,会调用InvocationHandler处理器的invoke方法。

1.4 拦截器注解的规则:

具体规则如下:

@Intercepts({
    @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
    @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
    @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
  • @Intercepts:标识该类是一个拦截器
  • @Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;
    • type:对应四种类型中的一种;
    • method:对应接口中的哪类方法(因为可能存在重载方法);
    • args:对应哪一个方法;

1.5. 拦截器可拦截的方法:**

拦截的类拦截的方法
Executorupdate, query, flushStatements, commit, rollback,getTransaction, close, isClosed
ParameterHandlergetParameterObject, setParameters
StatementHandlerprepare, parameterize, batch, update, query
ResultSetHandlerhandleResultSets, handleOutputParameters

2.实验自定义拦截器案例

谈到自定义拦截器实践部分,主要按照以下三步:

(1) 实现org.apache.ibatis.plugin.Interceptor接口,重写以下方法:

public interface Interceptor {
    Object intercept(Invocation var1) throws Throwable;
    Object plugin(Object var1);
    void setProperties(Properties var1);
}

(2) 添加拦截器注解@Intercepts{...}。具体值遵循上述规则设置。 (3) 配置文件中添加拦截器。

  • intercept(Invocation invocation)

从上面我们了解到interceptor能够拦截的四种类型对象,此处入参invocation便是指拦截到的对象。
举例说明:拦截**StatementHandler#query(Statement st,ResultHandler rh)**方法,那么Invocation就是该对象。

  • plugin(Object target)

这个方法的作用是就是让mybatis判断,是否要进行拦截,然后做出决定是否生成一个代理。

@Override
    public Object plugin(Object target) {
    //判断是否拦截这个类型对象(根据@Intercepts注解决定),然后决定是返回一个代理对象还是返回原对象。
//故我们在实现plugin方法时,要判断一下目标类型,如果是插件要拦截的对象时才执行Plugin.wrap方法,否则的话,直接返回目标本身。
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

注意:每经过一个拦截器对象都会调用插件的plugin方法,也就是说,该方法会调用4次。根据@Intercepts注解来决定是否进行拦截处理。

  • setProperties(Properties properties)

拦截器需要一些变量对象,而且这个对象是支持可配置的。


实战:自定义拦截器 在设计数据库字段时,往往会有表示创建时间或者更新时间的字段,假如名字分别叫create_timeupdate_time。如果在每个表的insert或update时,都要手动设置这些字段,则会很麻烦。
通过MyBatis拦截器,就可省去这些重复的步骤。拦截器能帮我们自动设置时间。

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class CustomInterceptor implements Interceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(EnvInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object object = invocation.getArgs()[1];
        //sql类型
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        if (SqlCommandType.INSERT.equals(sqlCommandType)) {
            //插入操作时,自动插入env
            Field fieldCreate = object.getClass().getDeclaredField("create_time");
            fieldCreate.setAccessible(true);
            fieldCreate.set(object, new Date());
        }else{
            if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
                //update时,自动更新updated_at
                Field fieldUpdate = object.getClass().getDeclaredField("updated_time");
                fieldUpdate.setAccessible(true);
                fieldUpdate.set(object, new Date());
            }
        }
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

注册

<plugins>
    <plugin interceptor="xxxx.CustomInterceptor"></plugin>
</plugins>