mybatis中Mapper接口调用的参数封装

接着我们上一次分享的mybatis源码阅读,这次我将通过阅读去了解我们在调用自己写的mapper接口的时候,mybatis具体是怎样与数据库交互的。

mapper接口的代理对象

大家都知道我们通过@mapper注解声明了mybatis的mapper接口,然后通过调用接口方法就能查询数据库。通过之前mybatis源码阅读文章,我们知道其实我们获取到的接口实现类是动态代理MapperProxy对象,而且是通过jdk的动态代理进行实现,所以当我们调用接口的时候,方法必然会执行到MapperProxy这个类中的invoke方法,方法实现如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        //调用invocke方法去调用MapperMethod的execute方法进行sql语句执行
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

cachedInvoker方法

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      //如果缓存中不存在调用方法,则缓存该方法
      return methodCache.computeIfAbsent(method, m -> {
        //mapper中的接口如果是default定义的默认方法,则使用 DefaultMethodInvoker ,否则使用 PlainMethodInvoker
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
       //我们一般编写的接口都不是用default关键字定义的,所以返回的是这个类
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

由上可知,执行接口的时候构造了PlainMethodInvoker类的一个对象实例,然后调用这个对象的invoke方法。最终调用到了MapperMethod类的execute方法,如下:

//TODO args就是在调用mapper接口方法时候传递进来的参数
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        //封装参数
        Object param = method.convertArgsToSqlCommandParam(args);
        //TODO 返回影响的行数
        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:
        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 {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

这个方法中增删改都调用了method.convertArgsToSqlCommandParam(args)进行参数的封装,其最终实现是调用了ParamNameResolver类的getNamedParams方法解析,具体如下:

public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    //TODO 不存在参数的情况
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      //TODO 一个参数的情况
      return args[names.firstKey()];
    } else {
      //TODO 多个参数的情况
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        //TODO key: @param注解的name属性值  value: 传入参数
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          //TODO key: parm1 parm2 ... value:传入的参数值
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }
}

以上方法实现其实很简单,Object[] args是调用接口传递进来的参数,例如mapper.findByUsernameAndAge(“李四”,25)调用,args就是[“李四”,25] 。
而names实际上就是一个map,实在解析xml的时候封装的,以下例子解释:

接口:findByUsernameAndAge(@Param("username")String username ,@Param("age")Integer age ); 
names中是这样的{ {0:username}{1:age}}

接口:findByUsernameAndAge(String username ,Integer age ); 
names中是这样的{ {0:arg0}{1:arg1}}

经过参数的封装,然后交给sqlSession 中持有的Executor进行数据库调用处理。

你可能感兴趣的:(mybatis系列)