MyBatis 入门 (八)

foreach 用法

为方便理解,本章涉及示例代码已上传至 gitee
==>获取示例代码请点击这里。。。
拉取示例代码时,请拉取所有分支,master 分支只是做了示例的初始化

先通过一段示例代码了解一下基本使用:

<select id="selectByIds" resultType="SysUser">
    select * from sys_user
        where
        1 = 1
        <if test="_parameter != null and _parameter.size() > 0 ">
        	and id in
            <foreach collection="list" item = "userId" open="(" close=")" separator="," index="index" >
            	#{userId}
            </foreach>
        </if>
</select>
public interface SysUserMapper {
    // other Methods ....

    List<SysUser> selectByIds(List<Integer> userIds);
}

之后便可以正常该使用了,foreach 标签的使用与其他标签一样,关于 foreach 标签的具体解析,请阅览 ForEachSqlNode 类的 apply(…) 方法;需要特别留意动态拼接的 SQL 的 规范性。


下面来看一下 MyBatis 在使用动态 SQL 标签时,是如何将接口参数与 SQL 的参数进行绑定的:

关于上图中,XML 中 SQL 的 if 判断时参数是:_parameter ,而在接口中,方法的参数是 userIds,关于 MyBatis 对 XML 文件中参数与接口方法中参数的处理是分为三个步骤来进行的:

MyBatis 接口方法中参数与 SQL 的绑定
  1. MyBatis 将通过接口传入的参数绑定到对应的动态上下文对象中
    在这里插入图片描述
    在参数加载阶段,最关键的是 步骤2 和 步骤4 这两个,在 步骤2 的时候,会将 Collection 和 Array 类型的参数封装成一个 Map 类型,然后会在 步骤四 的时候再次进行一次参数封装【该处的封装是对所有的参数进行封装,不论是什么类型,都会进行封装;而 步骤2 的封装仅针对于 不带@Param 注解的 Collection 或者 Array 类型的接口参数】,在此处只是会将 Map 类型的参数再次放到一个 ContextMap 对象中;value 值对应的 key 的名字为:_parameter
DynamicContext.class
// 对应上图 步骤4
// 当进入该方法时,parameterObject 参数时已经经过 wrapCollection(final Object object) 处理过了的
public DynamicContext(Configuration configuration, Object parameterObject) {
    if (parameterObject != null && !(parameterObject instanceof Map)) {
      MetaObject metaObject = configuration.newMetaObject(parameterObject);
      bindings = new ContextMap(metaObject);
    } else {
      bindings = new ContextMap(null);
    }
    // PARAMETER_OBJECT_KEY 值为 “_parameter”
    // parameterObject 是在 wrapCollection(final Object object) 中被组装的
    // 请留意此处:<if> 标签判断的 是此处的 parameterObject 是否为空
    bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
    bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
  }
// 对应上图 步骤2
// 如果你的 接口参数使用了 @Param 注解,则参数不会被该方法处理
private Object wrapCollection(final Object object) {
	// 如果 参数类型 是 Collection ,则给 Map 中添加一个 key 为 collection 类型的键值,同时也是 List 类型,再添加一个  key 为 list 键值
    // 也可以让 <if> 标签判断此处 key 为 collection/list/array 的 value,只需将 _parameter 更换为对应的 key 即可
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<>();
      map.put("collection", object);
      if (object instanceof List) {
        // 我们在上面的 foreach 标签中设置的 collection="list" ; 则 foreach 循环的是此处的 Object 对象
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      // 如果 参数类型 是 Array 类型,则添加一个 key 为 array 的键值
      StrictMap<Object> map = new StrictMap<>();
      // 此处与 map.put("list", object); 同理  
      map.put("array", object);
      return map;
    }
    return object;
  }

  1. 将参数值解析并添加到 BoundSql 对象中
    值得注意的一点是,MyBatis 在这里对参数进行 Map 类型的封装,不仅是为了解析动态标签时会使用;还会使用一个自定义的算法生成 key,value 为 Map 中 _parameter 下对应的参数,一个参数对应一个 key ,存放到 DynamicContext 类的 bindings 属性中,并在后续将该属性值依次赋给 BoundSql 类型的 metaParameters 对象,真正去拼装 SQL 和 参数时,参数的具体值是从 metaParameters 属性中获取的。
@Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 解析 context 中的 bindings 值,将其中涉及到的参数值;使用一个自定义的算法生成 key,value 为 参数值,并添加到 bindings 中
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      // 将参数添加到 boundSql 的 metaParameters 属性中
      // metaParameters 底层是通过 additionalParameters 来存储的 
      // 当 SQL 与 参数进行拼接时,会从该属性中获取到具体的 key 对应的 value 值
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }
  1. 将接口参数与 SQL 进行组装
    下图为 MyBatis 进行 SQL 和参数拼接的代码执行顺序
    在这里插入图片描述
    以下代码为接口参数与 SQL 拼接的具体步骤:
DefaultParameterHandler.class

@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 拿出 SQL 中赋值的参数列表
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          ...
          // 获取待赋值的参数名
          String propertyName = parameterMapping.getProperty();
          // 由于 metaParameters 底层是通过 additionalParameters 来存储的 
          // 如果 boundSql 中的 additionalParameters Map 中包含当前属性名,则拿出这个属性对应的 value
          if (boundSql.hasAdditionalParameter(propertyName)) { 
            value = boundSql.getAdditionalParameter(propertyName);
          }...
          
          try {
          	// 将 SQL 中对应位置的 ? 设置为 value 值
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } ...
        }
      }
    }
  }

除了使用 MyBatis 的默认 key 在标签中进行使用外,还可以使用注解方式,只需要在接口的参数前加上 @Param(“property”)即可,MyBatis 在执行时,会将 property 作为当前参数值的 key 进行存储,在解析 XML 时,通过属性名获取 value 值了。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值