【MyBatis】映射文件#参数传递方式(单表案例应用)#命名参数方式源码分析

笔记大纲
  • 参数传递方式
    • 单个普通类型参数
    • 多个普通类型参数
    • 命名参数(参数少建议使用注解)
    • POJO(常用)
    • Map(参数多建议使用)
    • Collection/Array
  • 参数传递源代码分析(重点)

1.参数传递方式

   我们在映射文件执行SQL语句时,需要传入对应的参数,满足语句时才会返回相应的值,就传入的参数类型、个数进行了以下情况的列举与分析。

1.1.单个普通类型参数

   如果参数是基本数据类型、包装类型、字符串类型等,MyBatis可直接使用这个参数,不需要任何处理。

   取值格式:#{随便写}

测试用例场景:根据id条件查询员工信息。

(1)mapper接口(单个参数查询)

在这里插入图片描述

(2)映射文件mybatis-conf.xml

在这里插入图片描述

(3)Junit测试
在这里插入图片描述

(4)输出符合预期结果

在这里插入图片描述

1.2.多个普通类型参数

   如果参数有多个时,会被MyBatis重新包装成一个Map传入,Map的key是param1,param2,或者是0,1…,对应的value就是参数的值。与单个普通类型参数传入的参数有所差异!!!

测试用例场景:根据id和lastname条件查询员工信息。

(1)mapper接口(多个参数查询)

在这里插入图片描述

(2)映射文件mybatis-conf.xml

在这里插入图片描述

(3)Junit测试

在这里插入图片描述

(4)不符合预期结果(报错

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [0, 1, param1, param2]
### Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [0, 1, param1, param2]

在这里插入图片描述

(5)(修改)映射文件mybatis-conf.xml中传入参数

在这里插入图片描述

(6)(修改后)符合预期结果
在这里插入图片描述

1.3.命名参数(参数少建议使用注解)

当参数添加后位置发生改变,传入的key是0,1,(上述1.2)就不能满足要求!应该采用命名参数的方式。

   为传入的参数使用注解@Param起名字,MyBatis会将这些参数封装到一个map中,key就是我们要指定的名字!有注解@Paramkey值就不是0,1方式取值,而是通过Param1Param2方式取值,这里需要注意细节。

(1)mapper接口(多个参数查询)

在这里插入图片描述

(2)映射文件mybatis-conf.xml

在这里插入图片描述

(3)Junit测试–测试通过

注意:此时传入的方式是0,1参数值,就会报错!

 select id,last_name,email,gender from employee_table where id =#{0} and last_name =#{1}
<org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.codinglin.mybatis.mapper.EmployeeMapper.selectEmployeeByIdAndName2

小结

1.2、1.3的传入参数的底层实现就是Map集合,如果是集合,作为开发人员,我们自己是可以自己封装!

1.4.POJO(常用)

数据传输对象(DTO:Data Transfer Object)

   当参数属于业务POJO时,就可以直接传递POJO取值。注意:如果参数有多个且经常被使用,一般会把参数参数封装到具体的对象进行传递。

   取值格式:#{JavaBean的属性名}

1.5.Map(参数多建议使用)

   如果参数有多个时,直接封装到一个map中,直接进行传递。

   取值格式:#{Map的key}

(1)mapper接口(多个参数查询)

在这里插入图片描述

(2)映射文件mybatis-conf.xml

在这里插入图片描述

(3)Junit测试

在这里插入图片描述
(4)符合预期结果
在这里插入图片描述

1.6.Collection/Array

   同上封装到一个map中,Collection对应的key是collection,Arrayn对应的key是array;

   如果是List集合,key还可以是list,MyBatis底层会封装Map,其常见规则:

keyvalue
Listcollection/list实际传递的参数值
Setcollection实际传递的参数值
Arrayarray实际传递的参数值
2.参数传递源代码分析

我选取了命名参数的传递方式进行了源代码的跟踪与分析!

mapper接口
在这里插入图片描述

mapper映射文件
在这里插入图片描述

测试类方法
在这里插入图片描述

(1)如何获取动态代理对象?

导入源码包:mybatis-3-mybatis-3.4.1_source.zip

 	//获取接口的代理实现类的实例============加上断点
		EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
---------------------------------------------------------------------------------------
@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this); //============F5进入方法
  }
-----------------------------------------------------------------------------------------
   @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);      //============F6运行下一行代码,单步调试
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);//=======单步调试截止,F5进入方法
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
-----------------------------------------------------------------------------------------
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);//============F6运行下一行代码,单步调试
    return newInstance(mapperProxy);//============单步调试截止,F5进入方法
  }  

 ---------------------------------------------------------------------------------------- 
    @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);//============通过newProxyInstance()方法获取代理对象
  }

(2)底层如何把参数存放到map中,进行封装?

//再此之前有自动装箱操作
//实现了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
  @Override
    //在invoke()中调用了目标对象的方法,完成了代理的过程
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {  //F6运行下一行代码,单步调试
         try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
      // arg是数组类型--Object[]
   return mapperMethod.execute(sqlSession, args);//单步调试截止,F5进入方法
       
 ----------------------------------------------------------------------------------------

在这里插入图片描述

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {//F6运行下一行代码,单步调试
      case INSERT: {
    	Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT://SELECT操作
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
            //转换为sql的命令参数
          Object param = method.convertArgsToSqlCommandParam(args);//单步调试截止,F5进入方法
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }

在这里插入图片描述

在这里插入图片描述

  public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);//F5进入方法
    }
  ----------------------------------------------------------------------------------  
  public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();//F6运行下一行代码,单步调试
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];
    } else {
      final Map<String, Object> param = new ParamMap<Object>();
      int i = 0;
        //map集合,这是底层帮我们封装好的!!!
      for (Map.Entry<Integer, String> entry : names.entrySet()) {//单步调试截止
          //第一次存放
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
           //第二次存放
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

在这里插入图片描述

在这里插入图片描述


 ☝上述分享来源个人总结,如果分享对您有帮忙,希望您积极转载;如果您有不同的见解,希望您积极留言,让我们一起探讨,您的鼓励将是我前进道路上一份助力,非常感谢!我会不定时更新相关技术动态,同时我也会不断完善自己,提升技术,希望与君同成长同进步!

☞本人博客:https://coding0110lin.blog.csdn.net/  欢迎转载,一起技术交流吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值