mybatis原理分析(五)---参数处理

1.概述

通过之前mybatis原理分析系列的博客,已经掌握了JDBC、Executor、StatementHandler,以及一级缓存和二级缓存。对于某一块不是和了解的读者可以到mybatis原理分析的专题中去查看相对应的博客。

在平时写代码的时候,我们并不会将一个sql语句中的参数给写死,而是写成如下的形式,通过传入参数的方法来给sql语句填上参数。

在这里插入图片描述

那么mybatis是如何做到的呢?一个参数和多个参数有没有什么区别?下面先介绍一下mybatis中关于参数处理的过程。

2.参数处理过程

参数处理会经过如下的步骤

在这里插入图片描述

  1. 参数转换

    即将接口方法中的参数,封装转换成Map,以便Map中的key和sql中的参数的引用相对应

  2. 参数映射

    解决Map中的key如何与sql中绑定的参数引用相对应。

  3. 参数赋值

    通过TypeHandler将sql语句中的参数引用替换成符合的数值。

3.源码分析

编写如下测试代码

在这里插入图片描述

在这里插入图片描述

3.1 参数转换

参数转换需要用到的重要类是ParamNameResolver,mybatis中接口的方法都是通过了代理对象来实现的。在代理的过程中会构建MapperMethod,会实例化一个ParamNameResolver对象。下图是ParamNameResolver的构造方法。

在这里插入图片描述

在这段代码中,最主要的就是给每个参数都确定了参数的名字。参数名字的规则如下:

  • 如果参数加上了@Param标签,则这个参数的名字就根据标签上的value属性中的值来确定。
  • 如果没有特殊声明的@Param标签,判断是否使用实际的名字(config.isUseActualParamName()),如果判断结果为true的话名字就是arg0,arg1。。。因为这是基于反射获取到的变量名字,不过在JDK8之后,如果javac编译时设置了 -parameters 编译参数,也可以直接获取源码中的变量名称作为参数的名字,这个例子中也就是“name” 和 “age”。
  • 如果上面的判断是false,那么就会使用参数的索引作为参数的名字。也就是“0”,“1”…

最后将参数的索引作为key,名字作为value,保存在names Map集合当中。这个Map是SortedMap,可以根据key值升序访问。也就是有序的遍历每一个参数名字。

之后在Mapper代理要执行增删改查也就是执行MapperProxy中的invoke方法的时候,会去获取名字以及对应的参数值。如下:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

下面看看convertArgsToSqlCommandParam(args)到底做了什么事情。

在这里插入图片描述

调用ParamNameResolver的获取已经命名的参数,包括参数名字和参数值。

在这里插入图片描述

这个方法逻辑也很简单。如果参数只有一个,那么根本就不需要知道参数到底是什么名字,只需要参数的值就可以了。所以直接返回这一个参数的值。

如果是多个参数,那么就需要去遍历刚才在实例化ParamNameResolver的时候得到的参数的名字。因为names是有序的集合。所以访问这里面参数的名字的顺序,和访问args参数值的顺序是一一对应的。也就是可以得到参数名字以及它们对应的参数值。将它们保存在一个Map当中。

除此之外,此时还额外生成了另外的参数名字。参数名字的前缀是“param”,是在ParamNameResolver中定义的一个常量。在前缀后面加上数字,从1开始。

所以这个方法执行完毕后,param这个map中有4对键值对,如下:

在这里插入图片描述

如果设置上 -parameters 编译参数,记得Rebuild Project,得到的结果如下:

在这里插入图片描述

我们先将上面设置的编译参数去掉,继续。。

如果加上了@Param注解呢

在这里插入图片描述

得到的结果如下:

在这里插入图片描述

至此就完成了参数处理的第一步。

如果是单参数的话直接返回参数值,如果是多参数的话,得到一个Map,参数名字以及对应的参数值,参数值可以是一个对象。

3.2 参数映射

接下来就是看如何将sql语句中的参数和这个Map集合对应上。

继续上面的代码执行

在这里插入图片描述
调用sqlSession的selectOne方法

在这里插入图片描述

不管是查询一个还是查询多个,都是调用selectList方法,根据结果返回值的个数,来返回具体的对象或者是集合。跟进selectList方法中

在这里插入图片描述

调用重载的方法,多了个参数,默认的返回行范围(全部)。

在这里插入图片描述

执行Executor的query方法

在这里插入图片描述

在创建缓存key的时候,需要获取到sql语句中参数所对应的参数值。

在这里插入图片描述

在获取参数值的时候,是根据sql语句中的参数名字来获取的
在这里插入图片描述
但是此时的map中没有“name”或者“age”的键

在这里插入图片描述

所以会报错,报错如下:

在这里插入图片描述

找不到参数name。

此时将UserMapper中的sql语句修改成如下:
在这里插入图片描述
此时就能获取到sql语句中参数arg0 对应的参数值了。

  • 问题来了?

    多个参数是通过如上的方式,来得到sql语句中的参数和Map中的值的。

    如果是单个参数呢?

    执行只有一个参数的方法来看看是如何实现的参数映射。

    在这里插入图片描述
    在这里插入图片描述
    可以看到在createCacheKey方法中,代码执行到了这里

    在这里插入图片描述

    对于单个参数而言,不用管sql语句中的参数到底是什么名字,因为仅有一个参数值可以与之匹配,所以直接将这个参数值和sql语句中的参数匹配上。

    而对于Object类型的呢?是如何做的

    执行一个参数是User类型的方法来看看是如何实现的参数映射

    在这里插入图片描述
    在这里插入图片描述

    在createCacheKey方法中,代码还是执行到了和多个参数相同的位置。
    在这里插入图片描述

    MetaObject 这个工具对象很强大,基于属性名称映射,支持嵌套对象属性访问。可以直接通过属性名字来获得属性值。例如:

    在这里插入图片描述

    在这里插入图片描述

测试完了单个参数、多个参数和对象参数的参数映射的不同。继续回到刚才多个参数的代码执行过程中来。

执行完createCacheKey,缓存key中就有了参数值。下图是执行了createCacheKey前后的缓存key对比

在这里插入图片描述
在这里插入图片描述

可以看到执行前,还没有参数“name” 和“age”的值,执行完之后,则找到了对应的参数值。这就是参数映射。

3.3 参数赋值

继续上面的代码执行,创建好了缓存key之后,就要执行另一个重载的query方法,具体的步骤在之前的博客中有介绍,此时我们关注点应该在给sql语句赋值上面。

在这里插入图片描述

代码定位到ReuseExecutor#doQuery方法中。

在这里插入图片描述

sql语句的参数赋值操作就是发生在预编译sql,构建statement的时候。

在这里插入图片描述

parameterize给statementHandler设置statement和参数,包括了给sql语句赋值。

在这个方法执行前,statement如下:

在这里插入图片描述

parameterize方法最终会执行到setParameters

在这里插入图片描述

在这个方法中,逻辑很简单,就是遍历sql语句中的参数引用名称,然后获取参数引用相对应的值,通过TypeHandler 为PrepareStatement设置值,通常情况下一般的数据类型MyBatis都有与之相对应的TypeHandler。

执行完之后,statement如下所示

在这里插入图片描述

这样就完成了参数赋值的操作,得到了完整的statement就可以执行JDBC的操作啦。

4.总结

  • 参数处理步骤

    1. 参数转换
    2. 参数映射
    3. 参数赋值
  • 参数转换总结

    1. 单个参数,默认不做处理,直接返回这个参数值,除非有@Param注解
    2. 多个参数
      1. 转换成param1、parma2
      2. 基于@Param 转换
      3. 基于反射转换变量名字,例如arg0、arg1 如果设置上 -parameters 编译参数,则会直接获取源码中的变量名字,而不是arg0、arg1
  • 参数映射总结

    1. 单个原始类型:直接映射,勿略SQL中参数引用名称
    2. Map类型:基于Map key映射
    3. Object:基于属性名称映射,支持嵌套对象属性访问(涉及到强大的工具类MateObject)
  • 参数赋值总结

    在执行JDBC操作之前,会预编译sql,构建Statement,其中包括了参数赋值,逻辑如下:

    遍历sql语句中的参数名称,根据名称从映射关系中获取相应的参数值,然后通过TypeHandler完成赋值操作。

5.后续

下一篇博客介绍mybatis结果集映射的原理,看看事如何将数据库返回的结果封装成java对象的。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
MyBatis是一款优秀的持久层框架,其主要作用是简化SQL语句的编写,提高数据访问效率。MyBatis的核心原理主要由以下三个部分组成: 1. SqlSessionFactory SqlSessionFactory是MyBatis的核心接口,其主要作用是创建SqlSession对象。SqlSession是MyBatis与数据库交互的工具,用于执行SQL语句、管理事务等操作。SqlSessionFactory的创建过程主要包括以下几个步骤: (1)读取mybatis-config.xml配置文件,解析其中的配置信息。 (2)读取Mapper.xml映射文件,解析其中的SQL语句和参数信息。 (3)构建Configuration对象,将解析出的配置信息封装到该对象中。 (4)创建SqlSessionFactory对象,将Configuration对象作为参数传入。 2. Mapper接口 Mapper接口是MyBatis中定义SQL语句的主要方式,其定义了与数据库交互的方法。在Mapper接口中定义的方法可以直接调用SqlSession中的方法来执行SQL语句。Mapper接口的定义方式主要有两种: (1)XML映射文件定义方式:将SQL语句和参数信息写在Mapper.xml文件中,然后在Mapper接口中定义方法,方法名与Mapper.xml文件中定义的SQL语句id保持一致。 (2)注解定义方式:直接在Mapper接口的方法上使用@Select、@Insert、@Update等注解定义SQL语句和参数信息。 3. Executor Executor是MyBatis中执行SQL语句的核心组件,其主要作用是处理SQL语句的执行和结果的返回。Executor主要包括以下几个实现类: (1)SimpleExecutor:简单执行器,每次执行SQL语句都会创建一个Statement对象。 (2)ReuseExecutor:可重用执行器,每次执行SQL语句不会创建新的Statement对象,而是从缓存中获取已经创建的Statement对象。 (3)BatchExecutor:批量执行器,将多个SQL语句批量执行,减少与数据库的交互次数,提高执行效率。 总体来说,MyBatis的核心原理主要包括SqlSessionFactory的创建、Mapper接口的定义和Executor的执行。通过这三个部分的协作,MyBatis可以轻松地实现与数据库的交互。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值