Mybatis 关于Bigdecimal类型 动态SQL != “” 导致不能更新(赋值)的问题,mybatis的动态SQL中””会被解析成0…

太长不看系列:在mybatis的动态SQL中""会被解析成0;

在mybtais中我们一般会用动态SQL处理自己的语句,也就是可以通过if语句判断是否成立,然后在进行赋值操作,
比如:

<if test="item.useNumDecimal != null and item.useNumDecimal != '' " >
                use_num_decimal = #{item.useNumDecimal},
            </if>

这里的意思就是判断useNumDecimal不是null并且也不等于 '' ",然后就把useNumDecimal的值赋值给use_num_decimal ;

如果useNumDecimal 是JAVA中的BigDecimal类型,这里的判断在其值为0的情况下就会造成误判,此处的 item.useNumDecimal 为0,”“也在mybatis的语法分析中也是0,这就导致0==0 而0 != 0的条件不成立,所以不会赋值;
那么'' "为什么会转换成0呢,
就是下面的源码导致的。注意doubleValue方法中的 return s.length() == 0 ? 0.0D : Double.parseDouble(s);

public static String stringValue(Object value, boolean trim) {
        String result;
        if (value == null) {
            result = OgnlRuntime.NULL_STRING;
        } else {
            result = value.toString();
            if (trim) {
                result = result.trim();
            }
        }

        return result;
    }


    public static double doubleValue(Object value) throws NumberFormatException {
        if (value == null) {
            return 0.0D;
        } else {
            Class c = value.getClass();
            if (c.getSuperclass() == Number.class) {
                return ((Number)value).doubleValue();
            } else if (c == Boolean.class) {
                return (Boolean)value ? 1.0D : 0.0D;
            } else if (c == Character.class) {
                return (double)(Character)value;
            } else {
                String s = stringValue(value, true);
                return s.length() == 0 ? 0.0D : Double.parseDouble(s);
            }
        }
    }

分析:
因为”“是String类型的且s.length() == 0成立,所以会转换成0.0D 也就是Doule类型的0,这也就是问题的根源;

处理方法只需要把这个and后面的判断去掉就好了也就是说把 and item.useNumDecimal != '' "去掉;

下面是Mybatis解析的一些思路及源码;
思路:

大致思路就是生成一个解析树,然后解析里面的值,看看条件是否成立,如果成立就把它生成到SQL语句中;

========================================================================

源码地址:

解析的入口在这里:

MappedStatement

  public BoundSql getBoundSql(Object parameterObject) {
    //获取SQL
    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;
  }
DynamicSqlSource
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    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) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }
StaticTextSqlNode
  @Override
  public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }

DynamicContext
//如果返回的值为true,则把SQL加入到sqlBuilder 中
  private final StringBuilder sqlBuilder = new StringBuilder();
  public void appendSql(String sql) {
    sqlBuilder.append(sql);
    sqlBuilder.append(" ");
  }


SimpleNode
  //获取值
    public final Object getValue(OgnlContext context, Object source) throws OgnlException {
        Object result = null;
        if (context.getTraceEvaluations()) {
            EvaluationPool pool = OgnlRuntime.getEvaluationPool();
            Throwable evalException = null;
            Evaluation evaluation = pool.create(this, source);
            context.pushEvaluation(evaluation);
            boolean var13 = false;

            try {
                var13 = true;
                result = this.evaluateGetValueBody(context, source);
                var13 = false;
            } catch (OgnlException var14) {
                evalException = var14;
                throw var14;
            } catch (RuntimeException var15) {
                evalException = var15;
                throw var15;
            } finally {
                if (var13) {
                    Evaluation eval = context.popEvaluation();
                    eval.setResult(result);
                    if (evalException != null) {
                        eval.setException((Throwable)evalException);
                    }

                    if (evalException == null && context.getRootEvaluation() == null && !context.getKeepLastEvaluation()) {
                        pool.recycleAll(eval);
                    }

                }
            }

            Evaluation eval = context.popEvaluation();
            eval.setResult(result);
            if (evalException != null) {
                eval.setException((Throwable)evalException);
            }

            if (evalException == null && context.getRootEvaluation() == null && !context.getKeepLastEvaluation()) {
                pool.recycleAll(eval);
            }
        } else {
            result = this.evaluateGetValueBody(context, source);
        }

        return result;
    }



 protected Object evaluateGetValueBody(OgnlContext context, Object source) throws OgnlException {
        context.setCurrentObject(source);
        context.setCurrentNode(this);
        if (!this._constantValueCalculated) {
            this._constantValueCalculated = true;
             //判断是否为常量
            boolean constant = this.isConstant(context);
            if (constant) {
                this._constantValue = this.getValueBody(context, source);
            }

            this._hasConstantValue = constant;
        }
        //不是常量就调用getValueBody方法
        return this._hasConstantValue ? this._constantValue : this.getValueBody(context, source);
    }


ExpressionNode
    //判断常量的方法
    public boolean isConstant(OgnlContext context) throws OgnlException {
        boolean result = this.isNodeConstant(context);
        if (this._children != null && this._children.length > 0) {
            result = true;

            for(int i = 0; result && i < this._children.length; ++i) {
                if (this._children[i] instanceof SimpleNode) {
                    result = ((SimpleNode)this._children[i]).isConstant(context);
                } else {
                    result = false;
                }
            }
        }

        return result;
    }


ASTAnd
    //获取getValueBody的方法
    protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
        Object result = null;
        int last = this._children.length - 1;

        for(int i = 0; i <= last; ++i) {
            result = this._children[i].getValue(context, source);
            if (i != last && !OgnlOps.booleanValue(result)) {
                break;
            }
        }

        return result;
    }




OgnlRuntime
//如果不是常量最终会通过method.invoke获得其值
   public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
        boolean syncInvoke = false;
        boolean checkPermission = false;
        synchronized(method) {
            if (_methodAccessCache.get(method) == null) {
                if (Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
                    _methodAccessCache.put(method, Boolean.FALSE);
                } else if (!method.isAccessible()) {
                    _methodAccessCache.put(method, Boolean.TRUE);
                } else {
                    _methodAccessCache.put(method, Boolean.FALSE);
                }
            }

            if (_methodAccessCache.get(method) == Boolean.TRUE) {
                syncInvoke = true;
            }

            if (_methodPermCache.get(method) == null) {
                if (_securityManager != null) {
                    try {
                        _securityManager.checkPermission(getPermission(method));
                        _methodPermCache.put(method, Boolean.TRUE);
                    } catch (SecurityException var12) {
                        _methodPermCache.put(method, Boolean.FALSE);
                        throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
                    }
                } else {
                    _methodPermCache.put(method, Boolean.TRUE);
                }
            }

            if (_methodPermCache.get(method) == Boolean.FALSE) {
                checkPermission = true;
            }
        }

        Object result;
        if (syncInvoke) {
            synchronized(method) {
                if (checkPermission) {
                    try {
                        _securityManager.checkPermission(getPermission(method));
                    } catch (SecurityException var10) {
                        throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
                    }
                }

                method.setAccessible(true);
//在这里可以通过反射得到对应的值 这个案例中得到的值就是0
                result = method.invoke(target, argsArray);
                method.setAccessible(false);
            }
        } else {
            if (checkPermission) {
                try {
                    _securityManager.checkPermission(getPermission(method));
                } catch (SecurityException var9) {
                    throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
                }
            }

            result = method.invoke(target, argsArray);
        }

        return result;
    }

ASTNotEq
    protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
        Object v1 = this._children[0].getValue(context, source);//0
        Object v2 = this._children[1].getValue(context, source);//因为是常量所以值为""
        //因为是and条件,所以判断两个值是否相等
        return OgnlOps.equal(v1, v2) ? Boolean.FALSE : Boolean.TRUE;
    }


OgnlOps
//判断是否相等的方法
   public static boolean equal(Object v1, Object v2) {
        if (v1 == null) {
            return v2 == null;
        } else if (v1 != v2 && !isEqual(v1, v2)) {
            if (v1 instanceof Number && v2 instanceof Number) {
                return ((Number)v1).doubleValue() == ((Number)v2).doubleValue();
            } else {
                return false;
            }
        } else {
            return true;
        }
    }

    public static boolean isEqual(Object object1, Object object2) {
        boolean result = false;
        if (object1 == object2) {
            result = true;
        } else if (object1 != null && object1.getClass().isArray()) {
            if (object2 != null && object2.getClass().isArray() && object2.getClass() == object1.getClass()) {
                result = Array.getLength(object1) == Array.getLength(object2);
                if (result) {
                    int i = 0;

                    for(int icount = Array.getLength(object1); result && i < icount; ++i) {
                        result = isEqual(Array.get(object1, i), Array.get(object2, i));
                    }
                }
            }
        } else {
            result = object1 != null && object2 != null && (object1.equals(object2) || compareWithConversion(object1, object2) == 0);
        }

        return result;
    }

//最终来到了这里
    public static int compareWithConversion(Object v1, Object v2) {
        int result;
        if (v1 == v2) {
            result = 0;
        } else {
            int t1 = getNumericType(v1);
            int t2 = getNumericType(v2);
            int type = getNumericType(t1, t2, true);
            switch(type) {
            case 6:
                result = bigIntValue(v1).compareTo(bigIntValue(v2));
                break;
            case 9:
                result = bigDecValue(v1).compareTo(bigDecValue(v2));
                break;
            case 10:
                if (t1 == 10 && t2 == 10) {
                    if (v1 instanceof Comparable && v1.getClass().isAssignableFrom(v2.getClass())) {
                        result = ((Comparable)v1).compareTo(v2);
                        break;
                    }

                    throw new IllegalArgumentException("invalid comparison: " + v1.getClass().getName() + " and " + v2.getClass().getName());
                }
            case 7:
            case 8:
                double dv1 = doubleValue(v1);
                double dv2 = doubleValue(v2);

                return dv1 == dv2 ? 0 : (dv1 < dv2 ? -1 : 1);
            default:
                long lv1 = longValue(v1);
                long lv2 = longValue(v2);
                return lv1 == lv2 ? 0 : (lv1 < lv2 ? -1 : 1);
            }
        }

        return result;
    }


    public static int getNumericType(Object value) {
        if (value != null) {
            Class c = value.getClass();
            if (c == Integer.class) {
                return 4;
            }

            if (c == Double.class) {
                return 8;
            }

            if (c == Boolean.class) {
                return 0;
            }

            if (c == Byte.class) {
                return 1;
            }

            if (c == Character.class) {
                return 2;
            }

            if (c == Short.class) {
                return 3;
            }

            if (c == Long.class) {
                return 5;
            }

            if (c == Float.class) {
                return 7;
            }

            if (c == BigInteger.class) {
                return 6;
            }

            if (c == BigDecimal.class) {
                return 9;
            }
        }

        return 10;
    }

    public static double doubleValue(Object value) throws NumberFormatException {
        if (value == null) {
            return 0.0D;
        } else {
            Class c = value.getClass();
            if (c.getSuperclass() == Number.class) {
                return ((Number)value).doubleValue();
            } else if (c == Boolean.class) {
                return (Boolean)value ? 1.0D : 0.0D;
            } else if (c == Character.class) {
                return (double)(Character)value;
            } else {
                String s = stringValue(value, true);

                return s.length() == 0 ? 0.0D : Double.parseDouble(s);
            }
        }
    }

    public static double doubleValue(Object value) throws NumberFormatException {
        if (value == null) {
            return 0.0D;
        } else {
            Class c = value.getClass();
            if (c.getSuperclass() == Number.class) {
                return ((Number)value).doubleValue();
            } else if (c == Boolean.class) {
                return (Boolean)value ? 1.0D : 0.0D;
            } else if (c == Character.class) {
                return (double)(Character)value;
            } else {
                String s = stringValue(value, true);
                  //在这里""->0
                return s.length() == 0 ? 0.0D : Double.parseDouble(s);
            }
        }
    }

如图 最终执行得到的结果是false;所以并不会进入赋值语句;

image.png