Mybatis如何执行Select语句,你真的知道吗?

简介: Mybatis如何执行Select语句,你真的知道吗?


前言

  • 本篇文章是Myabtis源码分析的第三篇,前两篇分别介绍了Mybatis的重要组件和围绕着Mybatis中的重要组件教大家如何阅读源码的一些方法,有了前面两篇文章的基础,来看这篇文章的才不会觉得吃力,如果没有看过的朋友,陈某建议去看看,两篇文章分别是Mybatis源码解析之六剑客Mybatis源码如何阅读,教你一招!!!
  • 今天接上一篇,围绕Mybatis中的selectList()来看一看Mybatis底层到底做了什么,有什么高级的地方。

环境准备

  • 本篇文章讲的一切内容都是基于Mybatis3.5SpringBoot-2.3.3.RELEASE
  • 由于此篇文章是基于前两篇文章的基础之上,因此重复的内容不再详细赘述了。

撸起袖子就是干

  • 二话不说,先来一张流程图,Mybatis六剑客,如下:
  • 上图中的这六剑客在前面两篇文章中已经介绍的非常清楚了,此处略过。为什么源码解析的每一篇文章中都要放一张这个流程图呢?因为Mybatis底层就是围绕着这六剑客展开的,我们需要从全局掌握Mybatis的源码究竟如何执行的。

测试环境搭建

  • 举个栗子:根据用户id查询用户信息,Mapper定义如下:
List<UserInfo> selectList(@Param("userIds") List<String> userIds);
  • 对应XML配置如下:
<mapper namespace="cn.cb.demo.dao.UserMapper">
    <!--开启二级缓存-->
    <cache/>
    <select id="selectList" resultType="cn.cb.demo.domain.UserInfo">
        select * from user_info where status=1
        and user_id in
        <foreach collection="userIds" item="item" open="(" separator="," close=")" >
            #{item}
        </foreach>
    </select>
</mapper>
  • 单元测试如下:
@Test
    void contextLoads() {
        List<UserInfo> userInfos = userMapper.selectList(Arrays.asList("192","198"));
        System.out.println(userInfos);
    }

DEBUG走起

  • 具体在哪里打上断点,上篇文章已经讲过了,不再赘述了。
  • 由于SpringBoot与Mybatis整合之后,自动注入的是SqlSessionTemplate,因此代码执行到org.mybatis.spring.SqlSessionTemplate#selectList(java.lang.String, java.lang.Object),如图1
  • 从源码可以看到,实际调用的还是DefaultSqlSession中的selectList方法。如下图2
  • 「具体的逻辑如下」
  1. 根据Mapper方法的全类名从Mybatis的配置中获取到这条SQL的详细信息,比如paramterType,resultMap等等。
  2. 既然开启了二级缓存,肯定先要判断这条SQL是否缓存过,因此实际调用的是CachingExecutor这个缓存执行器。
  • DefaultSqlSession只是简单的获取SQL的详细配置,最终还是把任务交给了Executor(当然这里走的是二级缓存,因此交给了缓存执行器)。下面DEBUG走到CachingExecutor#query(MappedStatement, java.lang.Object, RowBounds,ResultHandler),源码如下图3
  • 上图中的query方法实际做了两件事,实际执行的查询还是其中重载的方法List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql),如下图4
  • 根据上图源码的分析,其实CachingExecutor执行的逻辑并不是很难,反倒很容易理解,「具体的逻辑如下」
  1. 如果开启了二级缓存,先根据cacheKey从二级缓存中查询,如果查询到了直接返回
  2. 如果未开启二级缓存,再执行BaseExecutor中的query方法从一级缓存中查询。
  3. 如果二级缓存中未查询到数据,再执行BaseExecutor中的query方法从一级缓存中查询。
  4. 将查询到的结果存入到二级缓存中。
  • BaseExecutor中的query方法无非就是从一级缓存中取数据,没查到再从数据库中取数据,一级缓存实际就是一个Map结构,这里不再细说,真正执行SQL从数据库中取数据的是SimpleExecutor中的public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)方法,源码如下图5
  • 从上面的源码也是可以知道,在真正执行SQL之前,是要调用prepareStatement(handler, ms.getStatementLog())方法做一些参数的预处理的,其中涉及到了六大剑客的另外两位,分别是ParameterHandlerTypeHandler,源码如图6
  • 从上图可以知道设置SQL参数的真正方法是handler.parameterize(stmt),真正执行的是DefaultParameterHandler中的setParameters方法,由于篇幅较长,简单的说一下思路:
  1. 获取所有参数的映射
  2. 循环遍历,获取参数的值,使用对应的TypeHandler将其转换成相应类型的参数。
  3. 真正的设置参数的方法是TypeHandlersetParameter方法
  • 继续图6的逻辑,参数已经设置完了,此时就该执行SQL了,真正执行SQL的是PreparedStatementHandler中的<E> List<E> query(Statement statement, ResultHandler resultHandler)方法,源码如下图7
  • 上图的逻辑其实很简单,一个是JDBC执行SQL语句,一个是调用六剑客之一的ResultSetHandler对结果进行处理。
  • 真正对结果进行处理的是DefaultResultSetHandler中的handleResultSets方法,源码比较复杂,这里就不再展示了,具体的逻辑如下:
  1. 获取结果映射(resultMap),如果没有指定,使用内置的结果映射
  2. 遍历结果集,对SQL返回的每个结果通过结果集和TypeHandler进行结果映射。
  3. 返回结果
  • ResultSetHandler对结果处理结束之后就会返回。至此一条selectList()如何执行的大概心里已经有了把握,其他的更新,删除都是大同小异。

总结

  • Mybatis的源码算是几种常用框架中比较简单的,都是围绕六大组件进行的,只要搞懂了每个组件是什么角色,有什么作用,一切都会很简单。
  • 一条select语句简单执行的逻辑总结如下(前提:「默认配置」):
  1. 「SqlSesion」#SqlSessionTemplate.selectList()实际调用#DefaultSqlSession.selectList()
  2. 「Executor」#DefaultSqlSession.quer()实际调用的是#CachingExecutor().query(),如果二级缓存中存在直接返回,不存在调用#BaseExecutor.quer()查询一级缓存,如果一级缓存中存在直接返回。不存在调用#SimpleExecutor.doQuery()方法查询数据库。
  3. 「StatementHandler」#SimpleExecutor.doQuery()生成StatementHandler实例,执行#PreparedStatementHandler.parameterize()方法设置参数,实际调用的是#ParamterHandler.setParameters()方法,该方法内部调用TypeHandler.setParameter()方法进行类型转换;参数设置成功后,调用#PreparedStatementHandler.parameterize().query()方法执行SQL,返回结果
  4. 「ResultSetHandler」#DefaultResultSetHandler.handleResultSets()对返回的结果进行处理,内部调用#TypeHandler.getResult()对结果进行类型转换。全部映射完成,返回结果。
相关文章
|
2月前
|
SQL XML Java
【mybatis】第二篇:@Select注解中加入字段判断
【mybatis】第二篇:@Select注解中加入字段判断
|
SQL Java 数据库连接
mybatis @Select注解中当参数为空则不添加该参数的判断
public interface OrderMapper extends SqlMapper{ @Select("select * from tbl_order where room like #{room} and mydate like #{my...
4502 0
|
8月前
|
Java 数据库连接 mybatis
mybatis注解@Select中添加判断条件<script>
mybatis注解@Select中添加判断条件<script>
224 0
|
10月前
|
SQL XML Java
Mybatis-Plus select不去查全部字段和去重
Mybatis-Plus select不去查全部字段和去重
459 0
|
XML SQL Java
MyBatis-03 MyBatis XML方式之select元素
MyBatis-03 MyBatis XML方式之select元素
129 0
|
XML SQL 设计模式
Mybatis 中 .xml 文件定义 <select|insert|update|delete> 语句如何解析
Mybatis 中 .xml 文件定义 <select|insert|update|delete> 语句如何解析
241 0
Mybatis 中 .xml 文件定义 <select|insert|update|delete> 语句如何解析
mybatis@Select<script>写法
mybatis@Select<script>写法
817 0
|
SQL XML Java
MyBatisPlus中使用SELECT关联查询时未添加别名报异常的问题分析与修复
本文记录了在使用MyBatis表写入自定义的SQL查询语句时发生的异常问题。当定义使用include对主表中的字段进行包装时,在之后需要用到使用主表的关联查询,要将整个主表中include包装的字段都添加上别名,否则就会报错。本文记录在第一次使用MyBatisPlus框架时遇到的问题,是一次踩坑经验,希望帮助大家避免这样的坑。
1148 0
MyBatisPlus中使用SELECT关联查询时未添加别名报异常的问题分析与修复
|
SQL Java 测试技术
MyBatis——入门select
           前两天一直在搞AngularJs,各种看代码,昨天晚上要逼近崩溃的时候,决定看点儿别的调解下心情,就换到了MyBatis。         一,基本配置               1,引入myBatis的jar包(github地址:https://github.com/mybatis/mybatis-3/releases),我使用的是3.3.1这个版本。
1131 0
|
SQL 关系型数据库 数据库
03_MyBatis基本查询,mapper文件的定义,测试代码的编写,resultMap配置返回值,sql片段配置,select标签标签中的内容介绍,配置使用二级缓存,使用别名的数据类型,条件查询ma
 1 PersonTestMapper.xml中的内容如下: &lt;?xmlversion="1.0"encoding="UTF-8"?&gt; &lt;!DOCTYPEmapper PUBLIC"-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapp
1050 0