Mybatis参数传递介绍与源码分析

本博文基于:

MyBati3.5.5
JDK 1.8



一 ,介绍


在MyBatis中进行接口式编程时,在接口中写上自定义的参数,是如何进行参数处理的?
例如:当个参数的情况。

public Employee getEmployeeByID(Integer id);
<select id="getEmployeeByID" resultType="com.neil.bean.Employee">
        SELECT *
        FROM tbl_employee
        WHERE id = #{id}
    </select>

先给出结论

二 ,几种参数类型的用法


2.1 单个参数

  • 单个参数: Mybatis 不会做特殊处理 (参数名称可以随便的给出)
    #{参数名}: 取出你绑定的参数值。如上,即使是写成#{fsl}同样也可以返回(后文源码会分析
  • 多个参数: 多个参数就会被封装成Map形式数据。

​ key: param1… paramN, 或者参数的索引,notice:索引从1开始
​ value: 传入的参数值

2.2 多个参数

#{} 就是从Map中获取指定的key的值
【Example】:

操作:

​public Employee getEmpByIdAndLastName(Integer id, String lastName)

取值:

#{id},#{lastName}

会报错, Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]

解决错误方法,使用命名参数,由于上述方法中取出参数的方法都是通过param-[序号]的方式来进行指定,所以为了避免麻烦,我们可以通过明确指定封装参数时Map的key;@Param("id")

由于我们知道多个参数会封装成Map的形式,key值也有两种,所以,为了方便开发,可以通过注解@Param来指定Map所对应key的值 。

使用方式:

key: 使用@Param注解指定的值
​ value: 参数值
#{指定的key}取出对应的参数值

2.3 POJO(Plain Ordinary Java Object)

如果多个参数正好是我们的业务逻辑的数据模型,我们就可以直接传入POJO.

	#{属性值} :取出传入的POJO的属性值 

2.4 Map

如果是多个参数不是业务模型中的数据,没有对应的POJO,不经常使用,为了方便,我们也可以传入Map

   	 #{key}:取出Map中对应的值  

2.5 TO(Transfer Object)

如果多个参数不是业务模型中的数据,但是经常要使用,推荐编写一个TO(Transfer Object)数据传输对象

page{
   int index;
   int size;
}

三 ,例子分析

public Employee getEmp(@Param("id") Integer id, String lastName);
取值: id ====> #{id}或者是 #{param1}  lastName =====> #{param2}
public Employee getEmp(Integer id, @Param("e")Employee emp);
取值: id ====> #{param1}	lastName =====> #{param2.lastName} 或者是 #{e.lastName}

## 特别注意: 如果是Collection(List, Set)类型或者数组, 也会进行特殊处理, 也就是把传入的list或者数组封装在map中。

key : collection (collection),如果是List还可以使用这个key(list), 数组(array)

public Employee getEmpbyId(List<Integer ids>);
取值: 	取出第一个id的值: #{list[0]}


四, 源码分析


4.1 前置

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

4.2 定位

先从执行语句开始,通过代理对象Mapper来执行方法,会进入到代理对象中的invoke方法执行:
在这里插入图片描述

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return 
            // 判断当前是不是Object自带的方法,明显这里不是,执行false中的方法
           Object.class.equals(method.getDeclaringClass()) 
            ? method.invoke(this, args) 
            : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
      

this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession); 返回一个MapperMethodInvoker去调用 invoke方法,这里我们直接看。

  public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
            return this.mapperMethod.execute(sqlSession, args);

方法的执行流程全部都在execute中,
在这里插入图片描述
判断是否是哪种方法,这里我们直接找到
在这里插入图片描述
param所有的参数都是已经封装好的参数,所以,所有的逻辑都在convertArgsToSqlCommandParam方法中。

4.3 分析

所有的逻辑都在 ParamNameResolvergetNamedParams方法中:
在开始分析之前,我们要注意names成员变量获得的逻辑
在这里插入图片描述
names参数获得的逻辑,在构造方法中:

public ParamNameResolver(Configuration config, Method method) {
// paramcount获得参数的个数 
 for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
            if (!isSpecialParameter(paramTypes[paramIndex])) {
                String name = null;
              // 判断param注解个数
                Annotation[] var9 = paramAnnotations[paramIndex];
                int var10 = var9.length;

                for(int var11 = 0; var11 < var10; ++var11) {
                    Annotation annotation = var9[var11];
                    if (annotation instanceof Param) {
                        this.hasParamAnnotation = true;
                        // 将name赋值为value的值 这里例子为 id  lastName
                        name = ((Param)annotation).value();
                        break;
                    }
                }

                if (name == null) {
                // 判断有没有在setting中配置userActualParamName,
        //如果配置了useActualParamName=true的话,则取实际参数的名称
                    if (this.useActualParamName) {
                        name = this.getActualParamName(method, paramIndex);
                    }
			
                    if (name == null) {
                        name = String.valueOf(map.size());
                    }
                }
			// 最后赋值 map() , 本例子中得到的结果是 
			//{0 = id , 1 = lastname}
                map.put(paramIndex, name);
            }
        }

        this.names = Collections.unmodifiableSortedMap(map);

如果我配置的参数是
(Integer id, @Param(“lastName”) String lastName)
则,names的值为 {0 = 0, 1 = lastName}

后主要分析 getNamedParams方法:

public Object getNamedParams(Object[] args) {
        int paramCount = this.names.size();
    // 如果传入的参数为空则直接返回
        if (args != null && paramCount != 0) {
            // 如果只有一个元素,并且没有param注解:args[0]:单个参数直接返回,
            if (!this.hasParamAnnotation && paramCount == 1) {
                Object value = args[(Integer)this.names.firstKey()];
                return wrapToMapIfCollection(value, this.useActualParamName ? (String)this.names.get(0) : null);
                // 多个元素,或者有parm标注	
            } else {
                Map<String, Object> param = new ParamMap();
                int i = 0;
				// 保存数据
                for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
                    // 遍历names集合{0 = id , 1 = lastName}
                    Entry<Integer, String> entry = (Entry)var5.next();
                    // names的value作为key,name集合的key作为取值的参考 args中取出参数
                    param.put(entry.getValue(), args[(Integer)entry.getKey()]);
                    //{ id = 1, lastName = zhangsan, 2 = 2}
                    String genericParamName = "param" + (i + 1);
                    // 最后无论如何都放入param,是从1开始 
                    // 额外的将每一个参数都保存在map中,使用新的key: param1 , param2 
                    // 所以,除了可以使用#{key}之外,还可以使用#{param1 - N}来进行指定
                    if (!this.names.containsValue(genericParamName)) {
                        param.put(genericParamName, args[(Integer)entry.getKey()]);
                    }
                }

                return param;
            }
        } else {
            return null;
        }
    }

4.4 总结

ParamNameResover: 解析参数封装map

  1. names: {0 = id, 1 = lastName}: 构造器确定.
    确定流程:
    1. 获取每个标了param注解的参数的@Param的值:id, lastName , 赋值给name;如果没有,则默认赋值一个索引。
    2. 每次解析一个参数给Map中保存信息:(key:参数索引,value:name的值)
    3. name的值:
    标注了param的注解:注解的值
    ​ 没有标注:
    ​ 全局配置: userActualParamName: name 就是参数名(基于JDK1.8)

name= map.size():相当于元素的索引 {0 = id , 1 = lastName, 2 = 2};


本例中最后返回的param是在这里插入图片描述
notice:他会默认的给你赋值上param-[index],index从1开始

【推荐阅读】

MyBatisSQL参数占位符与全局属性useActualParamName详细介绍与原理剖析
mybatis 传递参数的7种方法
深入了解MyBatis参数
【参考】
https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#Parameters
https://www.cnblogs.com/mingyue1818/p/3714162.html

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值