
Mybatis-Plus的插件实现原理
前言
一个开源项目支持可扩展是很有必要的,可以基于此扩展来定制功能。就像Mybatis-Plus借助Mybatis简化开发,提升效率一样,就是在Mybatis的基础上进行了功能增强,其中MybatisPlusInterceptor就是一个扩展点。包括分页插件、多租户插件等等。
原理
首先,看下MybatisPlusInterceptor类,其实现了Mybatis的Interceptor实现sql前后的功能扩展。
Interceptor的加载
拦截器如何完成加载呢?
首先在配置文件中配置Bean,交由Spring管理MybatisPlusInterceptor。
在项目启动的时候,我们知道,会自动装配MybatisPlusAutoConfiguration类(SpringBoot自动配置),在MybatisPlusAutoConfiguration的实例创建时,如下:
interceptors会通过 ObjectProvider<Interceptor[]> interceptorsProvider进行赋值。
ObjectProvider是Spring提供的用于延迟获取bean的一个接口,此处通过 getIfAvailable() 方法获取 Interceptor 数组实例。
后面在进行SqlSessionFactory创建的时候,将interceptors添加到了InterceptorChain(拦截器链,get/set拦截器实例)的interceptors集合中。
至此,启动的过程中完成了MybatisPlusInterceptor的启动配置。
Executor的类结构
Executor是Mybatis中处理sql执行的4大核心组件之一,是MyBatis的执行器,它负责执行SQL语句。
还有另外三大组件ParameterHandler、StatementHandler、ResultSetHandler,分别用于对sql的参数处理、发送执行、结果转换。
Interceptor拦截Executor执行过程
1、在调用查询时,从openSqlSession()方法创建DefaultSqlSession开始看:
会进入
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
2、debug看下创建Executor
默认的executorType是SIMPLE。
首先创建SimpleExecutor,再创建CachingExecutor,将创建的SimpleExecutor传入给CachingExecutor的delegate变量。
同时将CachingExecutor对象传给BaseExecutor(SimpleExecutor的父类)的wrapper变量。
赋值后关系如下:
3、使用Plugin代理Executor
创建完CachingExecutor后,接着看(步骤2图中代码):
executor = (Executor) interceptorChain.pluginAll(executor);
3.1、此处的target是CachingExecutor,继续看调用MybatisPlusInterceptor的plugin方法
循环进行target的代理,此处是Plugin代理Executor
3.2、MybatisPlusInterceptor的plugin方法
如果目标对象属于Executor或者StatementHandler(依靠PreparedStatement执行sql的接口)
3.3、最后交由Plugin类完成动态代理
其中Plugin#signatureMap标识MybatisPluginInterceptor的注解,表示需要拦截的方法,有如下:
So,将来执行Executor的查询方法时,会调用Plugin的invoke方法执行代理行为。
经过了2,3步,完成了DefaultSqlSession创建,其中包括Executor的创建与代理,接下来继续跟进,在Executor执行的时候,Interceptor是如何进行拦截处理其扩展逻辑的。
4、在反射执行DefaultSqlSession查询时,交由Executor对象调用query查询,可以看到它是CachingExecutor的代理对象。
5、此时会进入到Plugin类的invoke方法,拦截器通过配置注解@Signature确定是否对当前的方法进行扩展
6、此时进入了MybatisPlusIntercepor的intercept,用于扩展自定义功能,可以对sql进行干预,是否执行等。
7、调用到CachingExecutor方法,如果满足缓存条件,则返回缓存数据(缓存的具体逻辑先跳过),进行进一步查询,进入SimpleExecutor
此处创建StatementHandler,其中prepareStatement()会进行数据库连接获取。(和Executor一样,StatementHandler也会交由Plugin进行代理,拦截器拦截StatementHandler的prepare和getBoundSql方法(见MybatisPlusInterceptor的注解)(具体StatementHandler的详细逻辑可以参考Executor进行debug))
贴一张StatementHandler的继承结构图:
执行完SimpleExecutor#doQuery()方法的“return handler.query(stmt, resultHandler);”便完成了正常的数据库查询。
至此,完成了数据库的查询与MybatisPlusIntercetor的拦截器扩展。
分页插件
再来看下分页插件的实现 PaginationInnerInterceptor
测试方法如下:
两个重写的核心方法
至此,分析了分页插件的实现过程,就是通过先count来决定要不要继续进行select,count的MappedStatement是有原selectd的MappedStatement生成的;在进行select时进行分页参数的拼接。
总结
1、MybatisPlusInterceptor实现了Mybatis的Interceptor接口完成了插件扩展,可以在执行sql前后进行干预,如多租户和分页等功能。
2、如果想扩展新的功能,可以实现Mybatis-Plus的InnerInterceptor接口,重写其方法,如willDoQuery、beforeQuery等。
3、介绍了分页插件的实现,通过出入参IPage进行判断及MybatisPlusInterceptor功能进行sql的干预实现。