mybatis的同参数名称的覆盖问题(foreach)

遇到个奇怪的问题。mapper.xml中是这么写的

<if test="statusList != null">
  AND STATUS IN
  <foreach collection="statusList" separator="," open="(" close=")" item="status" index="index">
	#{status ,jdbcType=TINYINT}
  </foreach>
</if>


<if test="status != null">
  AND STATUS = #{status,jdbcType=TINYINT}
</if>

当入参输入statusList([1,2,3])时,sql如下:

AND STATUS IN (1,2,3)AND STATUS = 3

很奇怪为什么status无值,反而在if判断时被判定为有值,而且为3,结合上面的条件,发现foreach的item中出现了同名的status,怀疑是否mybatis底层在做判断解析时,将item放入全局的参数中,导致出现参数的覆盖。于是开始源码分析。

 对于mybatis的执行原理详解,请阅读我的另一篇博文《通俗易懂的Mybatis工作原理》

按照mybatis的原理可知,最终是由sqlSession来执行sql,那么我们想要验证猜想,需要找到mybatis获取完整sql的地方,进行调试分析。以查询为例。 

// DefaultSqlSession

 @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // mapper.xml和mapper.java整合后的实体
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 执行器(默认创建的是CachingExecutor)执行查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

 

// CachingExecutor.query
@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 根据参数获取sql,这是我们重点分析的地方
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }



// MappedStatement.getBoundSql 
  public BoundSql getBoundSql(Object parameterObject) {
    // 根据参数获取sql,这是我们重点分析的地方
    // sqlSource类型为DynamicSqlSource
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

 

public class DynamicSqlSource implements SqlSource {

  private Configuration configuration;
  // 此方法对应命名空间的sql标签
  private SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    // 配置和参数的上下文
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 将各种条件标签解析,如:if/foreach等
    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.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

}

 

 

 

// ForEachSqlNode

// 解析
@Override
  public boolean apply(DynamicContext context) {
    // 上下文已经绑定的参数
    Map<String, Object> bindings = context.getBindings();
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
    // 拼接foreach中定义的open内容
    applyOpen(context);
    int i = 0;
    for (Object o : iterable) {
      DynamicContext oldContext = context;
      if (first) {
        context = new PrefixedContext(context, "");
      } else if (separator != null) {
        context = new PrefixedContext(context, separator);
      } else {
          context = new PrefixedContext(context, "");
      }
      int uniqueNumber = context.getUniqueNumber();
      // Issue #709 
      if (o instanceof Map.Entry) {
        @SuppressWarnings("unchecked") 
        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else {
        
        applyIndex(context, i, uniqueNumber);
        // 将item放入sql中
        applyItem(context, o, uniqueNumber);
      }
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) {
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      context = oldContext;
      i++;
    }
    applyClose(context);
    return true;
  }

  private void applyIndex(DynamicContext context, Object o, int i) {
    if (index != null) {
      context.bind(index, o);
      context.bind(itemizeItem(index, i), o);
    }
  }

  // !!! 将item拼接入sql并且将此item的key和value放入context的参数bindings中
  private void applyItem(DynamicContext context, Object o, int i) {
    if (item != null) {
      context.bind(item, o);
      context.bind(itemizeItem(item, i), o);
    }
  }

  private void applyOpen(DynamicContext context) {
    if (open != null) {
      context.appendSql(open);
    }
  }

  private void applyClose(DynamicContext context) {
    if (close != null) {
      context.appendSql(close);
    }
  }

  private static String itemizeItem(String item, int i) {
    return new StringBuilder(ITEM_PREFIX).append(item).append("_").append(i).toString();
  }

 

也就是说context的bindings是一个hashMap,用于存放参数,当执行foreach的解析时,会把foreach的item放入这个hashMap中,然后不停覆盖,直至保留最后一个值;如果参数中一开始定义了一个和item同名的参数,则会被覆盖。这也是为什么我的status没有赋值却依然显示有值的原因

 

欢迎大家和帝都的雁积极互动,头脑交流会比个人埋头苦学更有效!共勉!

公众号:帝都的雁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值