【Mybatis/Mybaits-Plus】【插件】插件执行时机一览
1 前言
我之前看过插件的执行过程:【Mybatis】【插件】Mybatis源码解析-插件机制,主要是通过一个 Executor 的创建以及执行过程串了一下插件,我们这里简单回忆下:
(1)Mybatis 把所有的插件都放进了 InterceptorChain 类里的 interceptors 集合里
(2)插件的两个时机:
- 入场时机:插件是在创建对象,比如创建出来 Executor实例后,然后执行 InterceptorChain ,判断是否要创建其代理,也就是通过创建其代理对象(JDK代理)进行增强,你有多少个插件就会创建几层代理对象,完成入场
- 执行时机:代理对象执行的时候会根据当前的方法,执行对应的插件
那么这节我们将看一下 Mybatis 所有的插件类型,并看看他们都是在 Mybatis 的哪个执行过程中可以做到什么样的事情,也就是有个全局的理解,并且也会串一下 Mybatis-Plus 里的插件。
另外本文里提到的 Mybatis 也包含 Mybatis-Plus 哈,我是以集成了 Plus 的服务看的,Plus本身也是在 Mybatis 的基础上套了一层,加了自己的逻辑,实际上都会执行到 Mybatis 的核心的,所以理解是应该偏差不大哈。
2 Mybatis
我们先看一下 Mybatis 运行 SQL 的一个执行过程,然后再看看每个插件类型分别都在执行过程中的哪个阶段进行介入的。
2.1 Mybatis 插件类型
我们先回忆下插件的类型和方法,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
有四种类型的插件,接下来我们就先看一个Mybatis的执行过程,看看这四个插件的时机。
2.2 Mybatis 执行过程
从哪开始呢?还是从 Mapper 接口开始吧,具体 Mapper 是如何一步步注入到 SpringBoot 中的,我们这里不讲,可以看我之前的三篇文章一步一步都详细讲解了,我这里主要画个图来从全局的看一下 Mapper接口的整个执行过程:
图越画越大了= =,红色的五角星表示执行过程中插件拦截器的入口,粉色的五角星表示插件拦截器链对当前对象是否要创建代理的入口,红色是执行时机,粉色是创建实际,创建时机一共四个还有一个执行器的创建我没画,它是在创建 SqlSession时创建的,之前文章有说过就略过了哈。
那么整体的执行过程语言描述下就是:
(1)Mapper 接口,出发点为其代理对象:MybatisMapperProxy ,这个对象本身有 SqlSessionFactory,而它里边又有 Configuration,有了 Configuration 其实就有了 Mybatis 的一切了。
(2)执行到代理的 invoke 方法,首先会用 MybatisMapperMethod 类包装一层,内部有一个重要的 SqlCommand,SqlCommand 在实例化的时候就会根据当前的接口以及方法找它的 MappedStatement,MappedStatement 是 Mybatis 早都初始化并且收集完信息的,有个属性 sqlCommandType 就知道当前 Mapper 要执行的是增删改查的哪一种了。
(3)然后就可以根据 SqlCommandType 来调用对应 SqlSession 的增删改查方法了。
(4)SqlSession 执行其实就是调用内部的 Executor 来执行,Executor 的执行和创建就涉及到插件拦截器的执行。
(5)Executor 执行增删改查的时候,又会创建 StatementHandler,就涉及到 StatementHandler 相关插件的执行。
(6)StatementHandler 的实例化或者构造器里,又会创建 ResultSetHandler 和 ParameterHandler 就涉及到这两者的代理创建。
(7)Statement 里执行前就会涉及 ParameterHandler 插件执行,执行完的结果又会通过 ResultSetHandler 进行处理。
(8)最后返回结果。
2.3 Mybatis-Plus 的插件
对于 Mybatis-Plus 里的插件,它有一个核心就是:MybatisPlusInterceptor (大管家),它内部有个自己的集合来装自己的 InnerInterceptor,也就是说对于 Mybatis-Plus 的插件,大家都实现 InnerInterceptor 即可。
// 拦截的类和方法 @Intercepts( { @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}), @Signature(type = StatementHandler.class, method = "getBoundSql", args = {}), @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @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}), } ) // Interceptor 就是 Mybatis 的接口 public class MybatisPlusInterceptor implements Interceptor { @Setter private List<InnerInterceptor> interceptors = new ArrayList<>(); .. @Override public Object intercept(Invocation invocation) throws Throwable { Object target = invocation.getTarget(); Object[] args = invocation.getArgs(); if (target instanceof Executor) { ... } ... @Override public Object plugin(Object target) { // Executor 和 StatementHandler 类型时才创建 if (target instanceof Executor || target instanceof StatementHandler) { return Plugin.wrap(target, this); } return target; } }
MyBatis-Plus 提供了以下插件:
- 自动分页:
PaginationInnerInterceptor
- 多租户:
TenantLineInnerInterceptor
- 动态表名:
DynamicTableNameInnerInterceptor
- 乐观锁:
OptimisticLockerInnerInterceptor
- SQL 性能规范:
IllegalSQLInnerInterceptor
- 防止全表更新与删除:
BlockAttackInnerInterceptor
当需要用到哪个时候,只需要把它放进 MybatisPlusInterceptor 即可。
3 小结
好啦,本节主要是对 Mybatis 里的插件结合 Mapper 的执行过程,有个整体的认识,有理解不对的地方欢迎指正哈。