mybatis的mapper方法参数解析过程源码


前言

Mybatis的mapper方法传递参数可以通过@param,对象object,map和list等方式。本文从源码层次分析每种参数传递的流程。

一、参数解析过程源码

参数解析的过程比较复杂,本文就重点代码进行分析。

public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      //通过@param和实体object传递参数都是在此处解析的,parameterMappings对应xml中的参数
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          //当boundSql含有额外参数时,mapper参数为list时
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            //传递参数使用@param时,parameterObject对应ParamMap(基于@param参数和默认key生成),使用map.get(prop.getName())取值,保证xml中变量名与@param中的名一致,或者使用arg0,arg1,param0
            //传递参数使用实体object时,parameterObject对应的时实体object,使用BeanWrapper基于属性反射获取,保证xml变量名与属性名一致
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            //获取参数对应的值
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }

除了使用list传递的参数,其余参数都是在metaObject.getValue(propertyName)方法解析的,下面是该方法的源码,其中最重要的方法是objectWrapper.get(prop),根据objectWrapper的不同字类调用不同的实现方法,其中@param和map方式使用的是MapWrapper,实体对象参数使用的是BeanWrapper,下面就两种ObjectWrapper的get方法进行分析。

public Object getValue(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        return null;
      } else {
        return metaValue.getValue(prop.getChildren());
      }
    } else {
      //根据不同的objectWrapper获取属性
      return objectWrapper.get(prop);
    }
  }

二、BeanWrapper和MapWrapper的get方法

1.BeanWrapper的get方法

       beanWrapper通过属性的get方法获取参数的值,需要保证xml中的属性名称与对象中的属性名称一致。

  public Object get(PropertyTokenizer prop) {
    if (prop.getIndex() != null) {
      Object collection = resolveCollection(prop, object);
      return getCollectionValue(prop, collection);
    } else {
      //通过反射获取,要保证xml中的变量名与对象属性名一致
      return getBeanProperty(prop, object);
    }
  }
 private Object getBeanProperty(PropertyTokenizer prop, Object object) {
    try {
      //获取属性的get方法
      Invoker method = metaClass.getGetInvoker(prop.getName());
      try {
        //反射调用get方法
        return method.invoke(object, NO_ARGUMENTS);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ".  Cause: " + t.toString(), t);
    }
  }

         当传入一个对象并且不使用@param注解的时候会使用这种方法,在开发中遇到了一个问题,Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'bids' in 'class java.lang.String',在该方法中将string类型的bids传入,mybatis将参数bids作为对象中的一个属性,所以使用getter方法获取,报没有getter方法异常.

    @Select("select TOP 1 * from Info with(nolock) where UserID in ${bids} and Status != 0 and Status != 6")
    Info getbids(String bids);

2.MapWrapper的get方法

MapWrapper的get方法直接调用了map的get方法,需要保证xml中的变量名与map中的key一致。

  public Object get(PropertyTokenizer prop) {
    if (prop.getIndex() != null) {
      Object collection = resolveCollection(prop, map);
      return getCollectionValue(prop, collection);
    } else {
      //直接调用map的get方法获取
      return map.get(prop.getName());
    }
  }

@param传递的参数也是通过MapWrapper的get方法解析,下面主要分析@Param传递参数时,parameterObject的生成过程。

三、@Param对应的ParamMap的生成过程

        对应paraMap生成的过程比较复杂,本文摘取其中的两段重要代码,首先是ParamNameResolver中的names的生成过程,如果使用了@param注解,其中的key使用@param注解中的值,否则使用arg0,arg1.....作为默认的key。

  public ParamNameResolver(Configuration config, Method method) {
    this.useActualParamName = config.isUseActualParamName();
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          //使用@param中的值作为map的key
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) {
        // @Param was not specified.
        if (useActualParamName) {
          //当@param没有使用时,使用arg0,arg1作为默认key
          name = getActualParamName(method, paramIndex);
        }
        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);
  }

     接下来getNameParams方法,创建了一个新的map,将上述names中的值加入到新的map中,而且额外添加了param1,param2.....为key的键值对,所以在xml文件中也可以通过param1,param2使用参数传递的值。

  public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      Object value = args[names.firstKey()];
      return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        //获取原有names中的值
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        //加入默认参数名param1,param2
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

 四、参数为list时参数解析过程

        参数为list时解析过程相对比较复杂,在上面参数解析源码中,通过boundSql.getAdditionalParameter方法获取值,该方法源码如下,其实与上面的方法一致,最终是通过MapWrapper的get方法获取值。

  public Object getAdditionalParameter(String name) {
    return metaParameters.getValue(name);
  }

接下来主要分析additionalParameter的赋值过程,主要时通过context.bind生成相关参数

  private void applyItem(DynamicContext context, Object o, int i) {
    if (item != null) {
      context.bind(item, o);
      //绑定参数,生成参数名
      context.bind(itemizeItem(item, i), o);
    }
  }
  private static String itemizeItem(String item, int i) {
    return ITEM_PREFIX + item + "_" + i; //__frch_item_0
  }
  public void bind(String name, Object value) {
    bindings.put(name, value);
  }
设置additionalParameter
context.getBindings().forEach(boundSql::setAdditionalParameter);

总结

       本文对mybatis中的mapper方法的各种参数传递方法进行了原理上的分析,并且在代码书写上给出建议,新手上路,解释不当之处请多谅解。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MyBatis是一个Java持久层框架,提供了灵活的SQL映射和便捷的数据库访问。Mapper接口是MyBatis中定义SQL映射的方式之一。下面是MyBatis Mapper源码分析的一般步骤: 1. 首先,需要了解Mapper接口和XML映射文件之间的对应关系。在XML映射文件中定义了SQL语句和结果映射规则,而Mapper接口则提供了对应的方法。 2. 接下来,可以分析Mapper接口的实现类。MyBatis会动态生成Mapper接口的实现类,这些实现类会根据XML映射文件中定义的SQL语句进行具体的数据库操作。 3. 在实现类中,可以找到具体的数据库操作方法。这些方法会通过SqlSession对象执行对应的SQL语句,并返回结果。 4. 在执行SQL语句之前,MyBatis会进行参数解析和类型转换等操作。可以分析这部分代码,了解参数处理的过程。 5. SQL语句的执行过程中,还涉及到一些与数据库连接相关的操作,比如连接的获取和释放。可以查看这部分代码,了解连接管理的实现方式。 6. 最后,可以分析SQL语句的执行结果处理过程MyBatis会根据XML映射文件中定义的结果映射规则,将查询结果转换成相应的Java对象。 需要注意的是,MyBatis源码比较庞大复杂,以上只是对Mapper源码分析的一般步骤。具体的分析过程可能会因版本和具体使用情况而有所不同。建议先阅读MyBatis的官方文档和相关资料,对其基本原理有所了解,再进行源码分析。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值