前置版本:
MybatisPlus3.0.5
前些日子写了两篇关于mybatis相关的文章,一篇是《sqlSessionFactory的创建过程》,一篇是《sql语句的创建过程 》,看这篇,建议把上面两篇看了再来 (。・∀・)ノ゙
这一次打算记录一下sql语句的执行流程,同样还是建议打开源码一起翻着对比看比较好
前戏
我们以如下代码开始入手:↓
1 | public boolean isExist(VisitInfoTable item) { |
首先,我们知道项目启动后,SpringBoot会装配对应的mapper接口文件和.xml文件到mybatis的configuration的mappedStatements中。里面有对应sql的全部内容,包括完整语句,拆分拼接语句等的内容。

在开始之前,mybatis的sql执行流程中必然会涉及到代理设计模式,具体的内容可以参考《代理模式》
正文
我们调用selectCount()方法,查询数据库的时候。
他会执行这个方法对应的代理对象。

他所对应的代理对象执行的类PageMapperProxy

因为他实现了InvocationHandler,所以他需要重写了invoke()方法
- 上来先判断他是否是Object类本身,如果是就不增强

- 判断缓存中是否有对应方法的缓存,如果缓存中没有,则创建一个,并添加到缓存中

- 调用
mapperMethod.execute方法执行sql

判断对应的sql执行类型:
- 首先上来会判断此次执行的sql是什么类型的操作

这次我们执行的操作是
Select操作,他细分了具体的内容,如是否有返回值/多个/map/标记等的具体划分![image]()
这次的查询都不符合,我们查询的是count数量,所以最后返回的是一个值,就执行
else里面的convertArgsToSqlCommandParam。一看方法名都能猜的出来,就是把对应的sql和我们的传入的参数做整合在一起

command.getName():获取到需要执行方法的全限定名
param:对应传入的参数
sqlSession.selectOne(command.getName(), param)这个方法又被代理

- 执行的AOP前后额外内容的结构如下:↓

获取对应sqlSession会话:
- 他在前置执行
获取了对应的sqlSession,在里面执行了对应的操作,并执行openSession(),打开获取这次会话

- 注意,这里的
autoCommit为false,不自动提交会话

- 根据对应的设置构造一个
DefaultSqlSession对象返回

- 返回sqlSession

- 发现
method.invoke(sqlSession, args),它又又又被代理了 3次!!!

selectList:
- 最后,他执行
selectList()

- 我们会发现,上面我们是查询一个返回对象,但是最后还是执行的selectList()方法。
- 但是,他会在下面对个数进行判断,如果是一个拿到集合第一个返回,如果多个就报TooManyResultsException错误,不然就返回null

- 他封装了两层的selectList(),传入的statement就是方法的全限定名,parameter顾名思义就是这次查询的参数

- 最最最最最最最最最!!!!关键的一步骤。

- MappedStatement你还熟悉吗???
configuration.getMappedStatement(statement) - 他根据方法的全限定名来拿到已经拼接好的sql语句

- 拿到的这个ms,就是这个方法对应的sql语句
1 | MappedStatement ms = configuration.getMappedStatement(statement); |
- 然后用执行
executor
1 | executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); |
但是发现,它又被代理了一层
![image]()
Plugin:资料
这个叫
Plugin的类是啥,我们看名字可以看出是插件这个
Plugin类的mybatis提供第三方进行拦截的插件类,加上插件就是通过这个类的wrap()方法

- 比如PageHelper分页插件,他就会有个
PageInterceptor

- 这里就是自定义的拦截器第三方插件了,他就循环遍历。

- 比如阿昌这里装了
PageHelper,那这里就会执行PageHelper的拦截器PageInterceptor的intercept方法

以上就是这次记录的全部内容,感谢你能看到这里!!
- 因为这里,我们并没有使用PageHelper的分页方法,所以就不会执行,会被放行,他会根据对应参数个数,来生产对应的
CacheKey

CacheKey:如下,根据各种信息拼接,最后成一个长字符串

- 肯定有老哥说,搞了半天怎么还没执行我们写的sql方法啊!!!!!
- 对于这里生成了对应的CacheKey后,他就会执行正式的方法了!!!。

executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql):↓

- 他先看看MappedStatement的
cache缓存里面有没有,那这里,因为我们是第一次执行,必然没有缓存

- 查询的过程,一开始会判断Executor关了没。没关就去缓存中拿,有就处理下返回,没有就去从数据库查。
- 这里会涉及到一个
queryStack,queryStack为0时就会清空本地的缓存clearLocalCache()

- 那我们看如果没有缓存,从数据库中查是什么逻辑
queryFromDatabase

他会给本地缓存先放一份这个内容,
Key:为我们之前生成的CacheKey
Value:
EXECUTION_PLACEHOLDER占位符localCache.putObject(key, EXECUTION_PLACEHOLDER);

- 然后他就去数据库查,他就怎么查呢,这里他会根据对应那种类型的Executor来有不同的逻辑,我们这里是用的
SimpleExecutor

- 根据ms获取到对应的mybatis的配置类信息,然后配置信息参数,sql语句等信息获取到对应的
StatementHandler,他就是实际负责操作 Statement 对象与数据库进行交流

有人肯定会不知道
Statement是什么,Statement 是Java 执行数据库操作的一个重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。Statement对象,用于执行不带参数的简单SQL语句。
- 现在有了Statement,和resultHandler结果处理器

- 最后执行
query,通过statement去执行对应的sql语句,再用resultSetHandler.handleResultSets来处理获取到的结果,并返回

- 接下来的执行就是JDBC的细节了,不在这次记录的范围
返回收尾流程:
- 执行完他会关闭
closeStatement做收尾操作


- 返回我们数据库查到的数据

下面就是,我们上面执行链路执行完,一路一路的返回结果:↓

- PageInterceptor.intercept

- Plugin.invoke

- DefaultSqlSession.selectList

- DefaultSqlSession.selectOne
这里就是判断他的结果是1个还是多个还是返回null

- SqlSessionTemplate.SqlSessionInterceptor
提交commit了我们之前开的sqlsession的会话

- PageMapperMethod.execute
判断sql类型的地方

- PageMapperProxy.invoke

- 最后返回到了我们业务这里的代码

至此,上面的一条sql执行的流程就算执行完毕了!
总结
阿昌这里记录下mybatis执行sql的流程
刚开始被代理,去先判断他是否是Object类本身
再判断缓存中是否有对应方法的缓存,如果缓存中没有,则创建一个,并添加到缓存中
判断对应的sql执行类型,判断为select类型
获取sqlSession对象,并打开sqlSession会话
执行selectList,从mybtis配置类中的MappedStatement执行getMappedStatement(statement)拿到方法全限定名对应的sql信息
执行Plugin类的mybatis提供第三方进行拦截的插件类的内容,如PageHelper等
从本地缓存中拿
有就从缓存中拿处理一下返回
没有就去查数据库
他会先给本地缓存先放一份这个内容,做占位符,
Key:为我们之前生成的CacheKey
Value:
EXECUTION_PLACEHOLDER占位符localCache.putObject(key, EXECUTION_PLACEHOLDER);
根据对应类型的Executor来有不同的方案,查询数据库
- 期间会拿到对应的Statement,和resultHandler结果处理器
- 最后执行
query,通过statement去执行对应的sql语句,再用resultSetHandler.handleResultSets来处理获取到的结果,并返回 - 然后就是JDBC的内容,就不多说了
closeStatement关闭Statement
根据queryStack,来是否清空本地缓存
然后就是一路返回结果
- 期间会提交sqlSession会话
- 最后关闭sqlSession会话
返回给业务结果
以上就是这次记录的全部内容,感谢你能看到这里!!!! ๑•̀ㅂ•́)


