MyBatis查询的意外问题 <if>标签 ==和=的区别

MyBatis查询的意外问题

使用MyBatis的注解写sql,像往常一样加入条件查询,如下:

"<if test=\"type!=null and type!=2\"> and (show_position=#{type,jdbcType=TINYINT} or show_position=2)</if>",
"<if test=\"type!=null and type=1\"> ORDER BY seq ASC</if>",

代码里注入参数,type是Byte类型的0.

执行时发生了什么?

==> Parameters: 1(Integer)

两条if都命中了,type实际注入的值是1

???

调试过程发现了一点问题,这些sql经过代理和切面的处理,最终将转化为预编译语句(PreparedStatement)。而注入参数的时候,一方面可以看到由方法注入的参数(代码传进去的参数),另一方面有一个BoundSql对象。注入参数会优先取BoundSql中的additional parameter。而在判断type!=null and type=1的时候,处理器认为这是一个赋值语句,其返回值是1,而不是false,并且也写到了BoundSql中的additional parameter,这就导致实际执行的sql type值为1.

如果是判断相等,这里应该使用==来判断,即可解决问题

附上一些代码:

DefaultParameterHandler

public void setParameters(PreparedStatement ps) throws SQLException {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          //******这里 优先从additional parameter取得参数值。因此实际type取得值是1
          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从parameter object取,也就是传递给方法的参数  
          } else {
            value = metaObject == null ? null : metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        }
      }
    }
  }

SimpleExecutor

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
      //编译PreparedStatement,并且注入参数,通过handler.parameterize方法
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

BoundSql生成的地方:MappedStatement

public BoundSql getBoundSql(Object parameterObject) {
  //sqlSource的类型是DynamicSqlSource
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings == null || parameterMappings.size() <= 0) {
    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;
}

DynamicSqlSource

public BoundSql getBoundSql(Object parameterObject) {
    //DynamicContext里面有一个bindings属性,这里只有两个参数:
    //_parameter ==> parameter object(map)
    //_databaseId ==> MySQL
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    //apply方法中进行所有if脚本的解析
    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;
  }

MixedSqlNode

public boolean apply(DynamicContext context) {
    //contents里面是各种SqlNode 比如IfSqlNode TextSqlNode等
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }

IfSqlNode

public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
        //这里的contents是<if>和</if>里面的语句。如果条件成立则把这些内容加入到context中
      contents.apply(context);
      return true;
    }
    return false;
}

ExpressionEvaluator

public boolean evaluateBoolean(String expression, Object parameterObject) {
    //就这个东西,不知道是啥,很魔性
    //如果是一个赋值语句,会把那个属性和值直接放到parameterObject里面
    //试了一把,类似于java表达式,他可以解析表达式的值
    //例如 type=1 返回值是1 type==1是false
    //parameterObject的作用就是提供参数值作比较
    Object value = OgnlCache.getValue(expression, parameterObject);
    //如果本身返回值是boolean 直接返回结果
    if (value instanceof Boolean) return (Boolean) value;
    //如果返回值是数字类型 那么非0为true 0为false 和常见的一些规则也是符合的
    if (value instanceof Number) return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);
    //如果返回值是其他类型 那么非空为true 空为false
    return value != null;
  }
上一篇:【java框架ssm-mybatis】使用mybait自定义拦截器实现分页功能


下一篇:一、mybatis的插件介绍