阿昌教你看懂mybatisplus的sql执行流程
阿昌 Java小菜鸡
## 前言

前置版本:MybatisPlus3.0.5

前些日子写了两篇关于mybatis相关的文章,一篇是《sqlSessionFactory的创建过程》,一篇是《sql语句的创建过程 》,看这篇,建议把上面两篇看了再来 (。・∀・)ノ゙

这一次打算记录一下sql语句的执行流程,同样还是建议打开源码一起翻着对比看比较好


前戏

我们以如下代码开始入手:↓

1
2
3
4
5
public boolean isExist(VisitInfoTable item) {
return baseMapper.selectCount(new QueryWrapper<VisitInfoTable>().
eq("ip",item.getIp()).
eq("time",item.getTime()))>0;
}

首先,我们知道项目启动后,SpringBoot会装配对应的mapper接口文件和.xml文件到mybatis的configurationmappedStatements中。里面有对应sql的全部内容,包括完整语句,拆分拼接语句等的内容

image

在开始之前,mybatis的sql执行流程中必然会涉及到代理设计模式,具体的内容可以参考《代理模式


正文

我们调用selectCount()方法,查询数据库的时候。

他会执行这个方法对应的代理对象。

image

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

image

因为他实现了InvocationHandler,所以他需要重写了invoke()方法

  • 上来先判断他是否是Object类本身,如果是就不增强

image

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

image

  • 调用mapperMethod.execute方法执行sql

image


判断对应的sql执行类型

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

image

  • 这次我们执行的操作是Select操作,他细分了具体的内容,如是否有返回值/多个/map/标记等的具体划分

    image

  • 这次的查询都不符合,我们查询的是count数量,所以最后返回的是一个值,就执行else里面的convertArgsToSqlCommandParam

  • 一看方法名都能猜的出来,就是把对应的sql和我们的传入的参数做整合在一起

image

command.getName()获取到需要执行方法的全限定名

image

param对应传入的参数

  • sqlSession.selectOne(command.getName(), param)这个方法又被代理

image

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

image


获取对应sqlSession会话

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

image

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

image

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

image

  • 返回sqlSession

image


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

image


selectList

  • 最后,他执行selectList()

image

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

image

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

image

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

image

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

image

  • 拿到的这个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()方法

image

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

image


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

image

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

image

以上就是这次记录的全部内容,感谢你能看到这里!!

  • 因为这里,我们并没有使用PageHelper的分页方法,所以就不会执行,会被放行,他会根据对应参数个数,来生产对应的CacheKey

image

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

image

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

image

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

image

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

image

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

image


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

image

  • 他会给本地缓存先放一份这个内容,

    • Key:为我们之前生成的CacheKey

    • Value:EXECUTION_PLACEHOLDER占位符

    • localCache.putObject(key, EXECUTION_PLACEHOLDER);

image

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

image

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

image

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

  • 现在有了Statement,和resultHandler结果处理器

image

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

image


返回收尾流程

  • 执行完他会关闭closeStatement收尾操作

image

image

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

image

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

image

  • PageInterceptor.intercept

image

  • Plugin.invoke

image

  • DefaultSqlSession.selectList

image

  • DefaultSqlSession.selectOne

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

image

  • SqlSessionTemplate.SqlSessionInterceptor

提交commit了我们之前开的sqlsession的会话

image

  • PageMapperMethod.execute

判断sql类型的地方

image

  • PageMapperProxy.invoke

image

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

image

至此,上面的一条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会话
  • 返回给业务结果


以上就是这次记录的全部内容,感谢你能看到这里!!!! ๑•̀ㅂ•́)

 请作者喝咖啡