Mybatis——Mapper代理

mapper的代理对象生成位于org.apache.ibatis.binding.MapperProxyFactory的newInstance方法,使用jdk的动态代理,代理的InvocationHandler为org.apache.ibatis.binding.MapperProxy。

调用代理对象的方法会走MapperProxy的invoke方法。

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {//执行继承object类的方法
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {//执行default方法
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //封装mapperMethod,执行mapper的接口方法
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

追踪到MapperMethod的execute方法,可以看到它是通过class+method的全路径得到对应的MappedStatement,然后通过MappedStatement的SqlCommandType和返回类型调用对用的SqlSession方法。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {//判断MappedStatement的SqlCommandType
      case INSERT: {//如果是insert调用sqlSession.insert
      	Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));//rowCountResult根据返回值返回int、long、boolean
        break;
      }
      case UPDATE: {//如果是update调用sqlSession.update
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {//如果是delete调用sqlSession.delete
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT://如果是select判断返回值和参数
        if (method.returnsVoid() && method.hasResultHandler()) {//如果返回值是void,且参数包含ResultHandler类型的参数,调用携带ResultHandler的SqlSession.select方法
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {//如果返回值为数组或者Collection,调用SqlSession的selectList方法
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {//如果返回值为Map且方法上由@MapKey注解,调用SqlSession的selectMap方法
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {//如果返回值为Cursor,调用SqlSession的selectCursor方法
          result = executeForCursor(sqlSession, args);
        } else {//调用SqlSession的selectOne方法
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        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;
  }

接下来我们看下面的代码:

	@Update("update user set username = #{username} and password = #{password} where id = #{id}")
    boolean updateById1(User user);

    @Update("update user set username = #{username} and password = #{password} where id = #{id}")
    boolean updateById2(@Param("eo")User user);

    @Update("update user set username = #{username} and password = #{password} where id = #{id}")
    boolean updateById3(Long id, User user);

    @Update("update user set username = #{username} and password = #{password} where id = #{id}")
    boolean updateById4(@Param("id")Long id, @Param("eo")User user);

运行结果会发现只有第一个方法可以运行成功,而后面会报错,如果改成下面会运行成功

	@Update("update user set username = #{username} and password = #{password} where id = #{id}")
    boolean updateById1(User user);

    @Update("update user set username = #{eo.username} and password = #{eo.password} where id = #{eo.id}")
    boolean updateById2(@Param("eo")User user);

    @Update("update user set username = #{param2.username} and password = #{param2.password} where id = #{param1}")
    boolean updateById3(Long id, User user);

    @Update("update user set username = #{eo.username} and password = #{eo.password} where id = #{id}")
    boolean updateById4(@Param("id")Long id, @Param("eo")User user);

导致这样结果的代码就在于Object param = method.convertArgsToSqlCommandParam(args);将参数转换为SqlSession的参数中。

我们往下追踪可以看到ParamNameResolver的getNamedParams:

public class ParamNameResolver {

  private static final String GENERIC_NAME_PREFIX = "param";

    //names结构为key为参数的index,value为参数名
    //例如:boolean updateById4(@Param("id")Long id, @Param("eo")User user); 会转换为{{0,"id"}, {1, "eo"}}
    //boolean updateById3(Long id, User user); value在jdk8以上且编译加上-parameters参数名为id,user,没有-parameters为无意义的arg0,arg1,而在jdk8以下由于获取不到参数名,所以取值为index
  private final SortedMap<Integer, String> names;

  private boolean hasParamAnnotation;

  public ParamNameResolver(Configuration config, Method method) {
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        //跳过rowbounds和resultHandler类型的参数
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
        //如果参数有@Param注解,使用param的value
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) {
        // 如果没有使用param获取参数名
        if (config.isUseActualParamName()) {
          name = getActualParamName(method, paramIndex);
        }
        //jdk8以下使用index
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }
    
  public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {//没有使用注解和参数只有一个,直接返回
      return args[names.firstKey()];
    } else {
      //将参数封装为paramMap,同时存储两份数据,一份为name-value,一份为param[index]-value
      final Map<String, Object> param = new ParamMap<Object>();
      int i = 0;
      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;
    }
  }
}

所以上面的接口参数会转换为

    boolean updateById1(User user);//->user

    boolean updateById2(@Param("eo")User user);//->paramMap{{"eo", user},{"param1", user}}

    boolean updateById3(Long id, User user);//jdk8以上没有开启parameters ->paramMap {{"arg0", id}, {"param1",id}, {"arg1", user}, {"param2",user}}

    boolean updateById4(@Param("id")Long id, @Param("eo")User user);//->paramMap{{"id", id}, {"param1", id}, {"eo", user}, {"param2", user}}
posted @ 2020-06-16 14:47  wuweishuo  阅读(199)  评论(0)    收藏  举报