阅前必读:
- 本文测试项目及相关总结资料,均放置在文末链接处。强烈建议去拽下来,看xmind脑图并结合源码进行理解。
- 本人是先绘制的xmind脑图,然后根据xmind脑图发的此博文,无论是可读性、还是层次感,xmind脑图都由于文字。
- Mybatis中逻辑很多,而本文重点关注的是Mybatis中SQL相关的逻辑,其余部分会简述或直接略过。
- 本文主要分享的内容是:
启动项目时,与SQL相关的逻辑
启动项目后,执行CURD方法时,与SQL相关的逻辑
六问Mybatis插件
- 文末链接指向的本人的测试项目,是长这样的:
(一)启动项目时,与SQL相关的逻辑:
1.1、首先,Mybatis会将Mapper接口中,每个方法对应的MappedStatement实例存入org.apache.ibatis.session.Configuration#mappedStatements中。
其中,key
为{全类名}.{方法名}
,如:com.aspire.ssm.mapper.SqlTestMapper.selectAll,value
为该方法对应的MappedStatement对象
。
注:其实同一个方法,同一个value,会存两次,一个长key(如上),一个短key(短key,只有方法名)。
注:对应源码可详见:org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement
。
- 1.1.1、 启动时,会
先解析出xml中的SQL对应的MappedStatement实例对象
,可详见源码:org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
。
具体的调用栈为:"main@1" prio=5 tid=0x1 nid=NA runnable java.lang.Thread.State: RUNNABLE at org.apache.ibatis.session.Configuration.addMappedStatement(Configuration.java:686) at org.apache.ibatis.builder.MapperBuilderAssistant.addMappedStatement(MapperBuilderAssistant.java:296) at org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode(XMLStatementBuilder.java:110) at org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(XMLMapperBuilder.java:137) at org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(XMLMapperBuilder.java:130) at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:120) at org.apache.ibatis.builder.xml.XMLMapperBuilder.parse(XMLMapperBuilder.java:94) at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.loadXmlResource(MapperAnnotationBuilder.java:182) at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parse(MapperAnnotationBuilder.java:129) at org.apache.ibatis.binding.MapperRegistry.addMapper(MapperRegistry.java:72) at org.apache.ibatis.session.Configuration.addMapper(Configuration.java:759) at org.mybatis.spring.mapper.MapperFactoryBean.checkDaoConfig(MapperFactoryBean.java:80) at org.springframework.dao.support.DaoSupport.afterPropertiesSet(DaoSupport.java:44)
- 1.1.2、
后解析出注解
(@Select、@Delete、@Update、@Insert、@SelectProvider、@DeleteProvider、@UpdateProvider、@InsertProvider)中的SQL对应的MappedStatement实例对象
,可详见源码:org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parseStatement
。具体的调用栈为:"main@1" prio=5 tid=0x1 nid=NA runnable java.lang.Thread.State: RUNNABLE at org.apache.ibatis.session.Configuration.addMappedStatement(Configuration.java:686) at org.apache.ibatis.builder.MapperBuilderAssistant.addMappedStatement(MapperBuilderAssistant.java:296) at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parseStatement(MapperAnnotationBuilder.java:356) at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parse(MapperAnnotationBuilder.java:139) at org.apache.ibatis.binding.MapperRegistry.addMapper(MapperRegistry.java:72) at org.apache.ibatis.session.Configuration.addMapper(Configuration.java:759) at org.mybatis.spring.mapper.MapperFactoryBean.checkDaoConfig(MapperFactoryBean.java:80) at org.springframework.dao.support.DaoSupport.afterPropertiesSet(DaoSupport.java:44)
1.2、其中,MappedStatement对象包含了许多与SQL相关的信息:
1.3、其中,sql信息在org.apache.ibatis.mapping.SqlSource#getBoundSql返回的BoundSql对象中:
1.4、由于SqlSource是一个接口,所以在项目启动时,放入Map中作为value的MappedStatement实例里的SqlSource对象其实是RawSqlSource或StaticSqlSource或ProviderSqlSource或DynamicSqlSource中的一个。
-
1.4.1、 RawSqlSource:内部持有了一个SqlSource,该SqlSource是StaticSqlSource实例。因为RawSqlSource在启动时就会计算出mapping(即:sql的最终的样子),所以其性能优于DynamicSqlSource。
普通的SQL,会被封装为RawSqlSource或者DynamicSqlSource
: -
1.4.2、 DynamicSqlSource:动态SQL处理器,会处理${}、#{}等一系列SQL,最终处理完毕后,会以最终的SQL信息等为参数,new一个StaticSqlSource来作为最终查询时用的SqlSource。
除了${}占位的普通SQL外,动态SQL全都会被封装为DynamicSqlSource
: -
1.4.3、 ProviderSqlSource:处理通过注解@InsertProvider、@DeleteProvider、@UpdateProvider、@SelectProvider写的SQL;ProviderSqlSource会转换为DynamicSqlSource或RawSqlSource,最终都会与StaticSqlSource关系起来。
以下SQL,会被封装为ProviderSqlSource
:
-
1.4.4、 ProviderSqlSource:StaticSqlSource:静态的SqlSource,无论是RawSqlSource、DynamicSqlSource还是ProviderSqlSource,最终都会与StaticSqlSource关系起来。都会“转换”成StaticSqlSource。
确切的说:RowSqlSource持有StaticSqlSource实例;DynamicSqlSource执行查询时,处理完动态SQL后会创建StaticSqlSource实例;ProviderSqlSource执行查询时,会转换为RowSqlSource或DynamicSqlSource。所以,MappedStatement#getBoundSql方法里面的sqlSource.getBoundSql(parameterObject),最终其实还是StaticSqlSource#getBoundSql
。注:RowSqlSource与DynamicSqlSource的区别是:SQL的结构会不会因为程序或参数值而变动。RowSqlSource:不会。DynamicSqlSource:会。
注:在项目启动后,执行CURD时,ProviderSqlSource会被转换为RowSqlSource或DynamicSqlSource。
-
1.4.5、 总结来说,即:
(二)启动项目后,执行CURD方法时,与SQL相关的逻辑:
2.1、假设,程序调用了此方法。
2.2、那么,第一步:程序调用Mapper中的CURD方法。
如:主动调用List selectAll_xml();方法进行查询。
2.3、第二步:由Mapper的代理对象,调用目标方法。
即com.sun.proxy.$Proxy86.selectAll_xml
,实际上是由org.apache.ibatis.binding.MapperProxy.invoke
进行调用的。
2.4、第三步:。。。。。。(一堆咱们此次并不关注的逻辑)。
2.5、第四步:获取方法对应的MappedStatement实例。以{全类名}.{方法名}为key,获取启动时存到org.apache.ibatis.session.Configuration#mappedStatements中的MappedStatement实例。
2.6、第五步:通过MappedStatement实例获取BoundSql对象(BoundSql对象中包含了SQL信息)。
首先,org.apache.ibatis.mapping.MappedStatement#getBoundSql:
注:MappedStatement#getBoundSql方法中,BoundSql boundSql = sqlSource.getBoundSql(parameterObject)返回的BoundSql 对象里面的SQL,是没有占位符#{xxx}的,原SQL中的#{xxx}会被?代替;MappedStatement#getBoundSql方法返回的BoundSql对象里面的SQL,也是没有占位符#{xxx}的,原SQL中的#{xxx}会被?代替。
注:MappedStatement#getBoundSql方法中,BoundSql boundSql = sqlSource.getBoundSql(parameterObject)返回的BoundSql 对象里面的SQL,是没有占位符${xxx}的,原SQL中的${xxx}会直接被具体的参数值代替MappedStatement#getBoundSql方法返回的BoundSql对象里面的SQL,也是没有占位符${xxx}的,原SQL中的${xxx}会直接被具体的参数值代替。
其次,我们知道在启动时,SQL信息被封装进的SqlSource实现只有RawSqlSource或DynamicSqlSource或ProviderSqlSource这三种,下面一次对他们进行分析。
-
2.6.1、 RawSqlSource#getBoundSql:
RawSqlSource#getBoundSql的调用方法栈为:
-
2.6.2、 DynamicSqlSource#getBoundSql:
-
2.6.2.1、 其中,org.apache.ibatis.scripting.xmltags.SqlNode#apply实现了动态SQL:
SqlNode接口有很多实现:
从这些类的名字就可看出,这些类的功能了。他们实现了对xml中if标签、choose标签、foreach等标签的动态判断处理。 -
2.6.2.2、 以IfSqlNode为例,讲解是如何实现动态SQL的:
-
2.6.2.3、 以IfSqlNode为例,讲解是如何实现动态SQL的:
"main@1" prio=5 tid=0x1 nid=NA runnable java.lang.Thread.State: RUNNABLE // 如果进一步跟踪, 会发现就是: 如果满足条件的话,就使用StringBuilder#append拼接SQL at org.apache.ibatis.scripting.xmltags.DynamicContext.appendSql(DynamicContext.java:66) at org.apache.ibatis.scripting.xmltags.StaticTextSqlNode.apply(StaticTextSqlNode.java:30) at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32) at org.apache.ibatis.scripting.xmltags.MixedSqlNode$$Lambda$389.584643821.accept(Unknown Source:-1) at java.util.ArrayList.forEach(ArrayList.java:1257) // 这里实现了动态SQL at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32) at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:39) at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:297) at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:82) at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) at com.sun.proxy.$Proxy99.query(Unknown Source:-1) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140) at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433) at com.sun.proxy.$Proxy81.selectList(Unknown Source:-1)
-
-
2.6.3、 ProviderSqlSource#getBoundSql:
其中invokeProviderMethod方法长这样:
所以,不论是RawSqlSource#getBoundSql还是DynamicSqlSource#getBoundSql还是ProviderSqlSource#getBoundSql,最终获得的都是StaticSqlSource#getBoundSql的结果。
2.7、第六步:执行查询。
2.8、第七步:。。。。。。(一堆咱们此次并不关注的逻辑)。
(三)六问Mybatis插件:
3.1、第一问:如何自定义插件?
注:本文的重点不是介绍如何自定义插件的,所以这里就简单介绍了。
3.2、第二问:如何将自定义的插件交由mybatis?
-
3.2.1、 方式一: 自定义插件,然后只需要将插件注册进入Spring容器即可,MybatisAutoConfiguration的会自动感知到容器中的插件,让后将其记录进org.apache.ibatis.session.Configuration#interceptorChain。
-
3.2.1.1、 首先,自定义插件并将其注册进容器:
-
3.2.1.2、 然后,MybatisAutoConfiguration的构造器会自动感知到容器中的插件:
提示:org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure依赖下的spring.factories文件中,指定了org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration,这就意味着程序启动时,会(考虑)自动注册MybatisAutoConfiguration类。 -
3.2.1.3、 然后,在后面的逻辑中,会将感知到的插件记录进org.apache.ibatis.session.Configuration#interceptorChain。
-
-
3.2.2、 方式二: 获取到容器中所有的SqlSessionFactory,然后通过SqlSessionFactory实例获取到Configuration实例,然后调用Configuration#addInterceptor添加自定义的拦截器。
-
3.2.3、 方式n: …
3.3、第三问:插件是什么时候绑定到四大对象的?
提示一: 所有的Mybatis插件都会在Mybatis启动时,记录进org.apache.ibatis.session.Configuration#interceptorChain。
提示二: Mybatis插件是SqlSession级别的,所以在执行SQL时,才会在SqlSession中真正应用给四大拦截对象(Executor或StatementHandler或ParameterHandler或ResultSetHandler)
。
-
3.3.1、 启动Mybatis时,记录所有插件:org.apache.ibatis.session.Configuration的interceptorChain属性实例中,维护了一个ArrayList;Mybatis所有的插件,都会在启动时记录进这个ArrayList中。
-
3.3.2、 启动Mybatis后,执行SQL方法时,绑定插件给四大对象:
-
3.3.2.1、 插件应用给Executor的时机:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke中getSqlSession时。更准确的说,是【时机是】Configuration#newExecutor时,可以看一下相关方法调用栈细节:
"main@1" prio=5 tid=0x1 nid=NA runnable java.lang.Thread.State: RUNNABLE at org.apache.ibatis.plugin.InterceptorChain.pluginAll(InterceptorChain.java:30) // 初始化插件(将插件与Executor关联起来) at org.apache.ibatis.session.Configuration.newExecutor(Configuration.java:599) at org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource(DefaultSqlSessionFactory.java:96) at org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession(DefaultSqlSessionFactory.java:57) at org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:98) // 获取SqlSession时 at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:428) at com.sun.proxy.$Proxy81.selectList(Unknown Source:-1) at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230) at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:58) // 代理对象调用方法 at com.sun.proxy.$Proxy86.selectAll_xml(Unknown Source:-1) // 代理对象调用方法 at com.aspire.ssm.SsmApplicationTests.testOne(SsmApplicationTests.java:24) // 触发查询
-
3.3.2.2、 插件应用给ParameterHandler、ResultSetHandler、StatementHandler的时机:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke中,getSqlSession后,查询动作完成之前。更准确的说,【时机分别是】Configuration#newParameterHandler时、Configuration#newParameterHandler时、Configuration#newParameterHandler时。可以看一下相关方法调用栈细节:
"main@1" prio=5 tid=0x1 nid=NA runnable java.lang.Thread.State: RUNNABLE at org.apache.ibatis.plugin.InterceptorChain.pluginAll(InterceptorChain.java:30) // 应用插件(将插件与StatementHandler关联起来) /* * ************************************************************ * 在这之间会(按顺序)完成【应用插件(将插件与ResultSetHandler关联起来)】、【应用插件(将插件与ParameterHandler关联起来)】 * 注: 可详见源码org.apache.ibatis.session.Configuration.newStatementHandler */ at org.apache.ibatis.plugin.InterceptorChain.pluginAll(InterceptorChain.java:30) at org.apache.ibatis.session.Configuration.newResultSetHandler(Configuration.java:571) // 应用插件(将插件与ResultSetHandler关联起来) at org.apache.ibatis.executor.statement.BaseStatementHandler.<init>(BaseStatementHandler.java:70) at org.apache.ibatis.session.Configuration.newParameterHandler(Configuration.java:564) // 应用插件(将插件与ParameterHandler关联起来) at org.apache.ibatis.executor.statement.BaseStatementHandler.<init>(BaseStatementHandler.java:69) at org.apache.ibatis.executor.statement.PreparedStatementHandler.<init>(PreparedStatementHandler.java:41) at org.apache.ibatis.executor.statement.RoutingStatementHandler.<init>(RoutingStatementHandler.java:46) // ************************************************************ at org.apache.ibatis.session.Configuration.newStatementHandler(Configuration.java:577) at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:61) // 触发查询方法 at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324) at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156) at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109) at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:108) at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) at com.sun.proxy.$Proxy99.query(Unknown Source:-1) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140) at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) // 获取SqlSession后, 触发查询方法 at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433) at com.sun.proxy.$Proxy81.selectList(Unknown Source:-1) at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230) at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:58) // 代理对象调用方法 at com.sun.proxy.$Proxy86.selectAll_xml(Unknown Source:-1) // 代理对象调用方法 at com.aspire.ssm.SsmApplicationTests.testOne(SsmApplicationTests.java:24) // 触发查询
-
3.3.2.3、 将上面两个分支中涉及到的org.apache.ibatis.plugin.InterceptorChain#pluginAll(Object target)单独拿出来,继续钻细节:
InterceptorChain#pluginAll的关键调用栈:"main@1" prio=5 tid=0x1 nid=NA runnable java.lang.Thread.State: RUNNABLE at org.apache.ibatis.plugin.Plugin.wrap(Plugin.java:46) at com.aspire.ssm.plugins.MyExecutorPlugin.plugin(MyExecutorPlugin.java:46) at org.apache.ibatis.plugin.InterceptorChain.pluginAll(InterceptorChain.java:31)
- 3.3.2.3.1、 target = interceptor.plugin(target)方法,即:我们自定义的插件里面的plugin方法:
- 3.3.2.3.2、 target = interceptor.plugin(target)方法,即:我们自定义的插件里面的plugin方法:
-
3.3.2.3.2.1、 A处的作用是:获取到当前插件对象上@Intercepts注解里,@Signature的信息,即:获取到下图自定义插件中这个位置里面的信息:
注:源码可详见org.apache.ibatis.plugin.Plugin#getSignatureMap。 -
3.3.2.3.2.2、 A返回的signatureMap里,数据大致长这样:
-
3.3.2.3.2.3、 B处的作用是:决定是否把当前interceptor插件,绑定到当前target对象上。若返回的interfaces长度大于0,则需要绑定,否者不需要绑定。
getAllInterfaces方法返回interfaces的逻辑是这样的:
逻辑一:获取target对象的所有接口,使知道target代表了四大对象中的谁(可以只代表一个、也可以同时代表多个)。
问:为什么是获取target的所有接口?
答:我们知道,target实际上是四大对象的子类实现。四大对象分别是Executor、ParameterHandler、ResultSetHandler、StatementHandler,他们都是接口。所以,这里获取到target实现的所有接口后,就知道target是属于四大对象中的哪个(或哪些)对象了。简单的讲,就是让程序知道target代表了四大对象中的谁(可以只代表一个、也可以同时代表多个)。
逻辑二:通过A处得到的signatureMap,进一步请Class交集,若存在,则添加至集合interfaces中,并返回。
问:signatureMap在getAllInterfaces方法中发挥的作用是什么?
答:首先,signatureMap是我们在前面的A中获得的,这里面的信息代表了:当前Interceptor的绑定方向(或者说处理能力)。即:当前Interceptor实例只能用于,在这个signatureMap的keys中存在的类。因为我们在自定义插件时,@Signature指定的type为四大对象,所以这里signatureMap中的keys也只可能是四大对象。
这样一来,对这两者求交集,就能知道:是否应该把当前Interceptor插件绑定到当前target实例上了 -
3.3.2.3.2.4、 如果需要绑定的话,就将当前interceptor插件,绑定到当前target对象上;并返回,作为新的target,继续遍历下一个插件。
绑定当前插件至此target,同时生成新的target并返回:
for循环下一个插件:
注:这里可以看到【装饰者模式】,插件对原target进行层层装饰,返回装饰后的新的target。
-
- 3.3.2.3.1、 target = interceptor.plugin(target)方法,即:我们自定义的插件里面的plugin方法:
-
3.4、第四问:是在什么时候走插件中的逻辑的?
在target对象(即:四大对象)执行@Signature指定的方法时
。
-
3.4.1、 分析:
-
3.4.1.1、 假设插件的@Signature是这样的:
-
3.4.1.2、 那么,当target对象属于四大对象中的Executor,且当其执行Executor#query(MappedStatement, Object, RowBounds, ResultHandler)或Executor#query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)方法时:
根据当前对象执行的方法,判断是否触发当前插件的逻辑。可详见源码org.apache.ibatis.plugin.Plugin#invoke:
注:在【第三问】中,我们知道了插件是何时绑定到对象上的。但是,程序运行毕竟是方法上的,光知道插件与对象的对应关系,还不行;还得知道插件和该对象的方法的对应关系。此处就是知道插件与对象的方法的对应关系的,如果对应,则执行插件的逻辑;否者不执行。 -
3.4.1.3、 会触发该插件的逻辑,进入插件中的方法:
-
3.4.1.3.1、 参数Invocation说明:
方法Interceptor#intercept的参数Invocation,是下图这里传的:
可以看到,一个Invocation对象里,包含了:
target:真正要调用的对象
。
method:要调用的方法
。
args:方法的参数
。 -
3.4.1.3.2、 A处是此插件的前处理逻辑。
-
3.4.1.3.3、 B处invocation.proceed,表示:让程序继续往下执行:
提示一:这一步背后的逻辑,用到了【责任链模式】,也可以理解为逆向打开【装饰者模式】。
提示二:可以参考Filter的机制进行理解。
举例说明,这一步背后的逻辑
:
假设:
同时有3个插件针对当前对象(注:当前对象属于四大对象)的当前方法生效。那么
,原target对象被这三个插件装饰(包裹)后的新的target是这样的:
此处背后的逻辑(方法栈)为:
-
3.4.1.3.4、 C处是此插件的后处理逻辑。
-
-
-
3.4.2、 结论:
当执行方法的对象是插件的@Signature注解里指定的对象,且执行的方法是@Signature注解里指定的对象的指定方法时;会先执行插件的前处理逻辑,然后再继续执行目标方法,然后再执行插件的后处理逻辑。当该对象的该方法同时被多个插件拦截时,会像链条一样(责任链),先按顺序执行所有插件的前处理逻辑,然后再继续执行目标方法,然后再按顺序执行所有插件的后处理逻辑。
注:前处理逻辑、后处理逻辑的位置如图所示:
多个插件时,执行顺序如图所示:
提示:哪个插件在外层,哪个插件在里层,可详见【第五问】。
3.5、第五问:当多个插件,同时绑定到了同一个对象上时,这些插件的执行先后顺序是什么?
-
3.5.1、 分析:
在【插件绑定到四大对象】时,会调用org.apache.ibatis.plugin.InterceptorChain#pluginAll给target(注:当前target对象,为四大对象之一)应用上插件。
因为是通过foreach循环的有序列表ArrayList中的所有的插件。那么,ArrayList中,index越小的插件,就越先应用给target对象。 -
3.5.2、 结论:
- 3.5.2.1、 插件的Order值越小,就会越早注册进Spring容器,就会越早add进ArrayList,就会越早应用给target对象,插件就会越"贴近"target对象。
- 3.5.2.2、 越先应用给target对象的插件,就越在里面;越在里面的插件,当【the way in】时,其的前处理逻辑,就越晚执行,但当【the way out】时其后处理逻辑就越早执行。
- 3.5.2.3、 示例证明:
准备三个插件,并运行测试。
- MyExecutorPlugin:
- MyExecutorPlugin2:
- MyExecutorPlugin3:
- 运行测试(随便执行一个SQL,观察日志输出):
观察日志可知,结论正确。
3.6、第六问:以Executor为例,在很多实现(如BaseExecutor)里面存在内部调用的情况(如下面第一图),那么当插件里面如(下面第二张)图设置的时候,会不会走两遍重复的逻辑?
答:不会。因为Mybatis插件实际上是代理模式的一种实现。内部调用,是不会走代理的,所以如左图1中所示,当外部调用了BaseExecutor的4个参数的query方法时,虽然4个参数的query方法内部调用了6个参数的query方法,但是Mybatis插件(即:代理)的逻辑还是只走一遍。
Mybatis之一个SQL的运行过程,梳理完毕 !
^_^ 如有不当之处,欢迎指正
^_^ 参考资料
《Mybatis源码》
^_^ 测试代码托管链接
https://github.com/JustryDeng…Mybatis…
^_^ 本文已经被收录进《程序员成长笔记》 ,笔者JustryDeng