前置版本:
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/标记等的具体划分这次的查询都不符合,我们查询的是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); |
但是发现,它又被代理了一层
Plugin:资料
这个叫
Plugin
的类是啥,我们看名字可以看出是插件这个
Plugin
类的mybatis提供第三方进行拦截的插件类,加上插件就是通过这个类的wrap()方法
- 比如PageHelper分页插件,他就会有个
PageInterceptor
- 这里就是自定义的拦截器第三方插件了,他就循环遍历。
- 比如阿昌这里装了
PageHelpe
r,那这里就会执行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会话
返回给业务结果
以上就是这次记录的全部内容,感谢你能看到这里!!!! ๑•̀ㅂ•́)