工作中遇到一个mybatis的问题,当实体类中数据类型为Double的字段传入参数为0时,查询或更新未生效。代码如下:
<result property="billMoney" column="bill_money" javaType="java.lang.Double" jdbcType="NUMERIC"/>
<if test="billMoney != null and billMoney !=''">
and bill_money = #{billMoney}
</if>
在网上查了相关资料,大部分是说mybatis把0认为是空字符串了,所以当传入字段值为0的时候,if test判断为false,所以sql未能执行,导致更改或查询未生效。
一般解决方案有下面两个:
方案1.去掉空字符串:
<if test="billMoney != null">
and bill_money = #{billMoney}
</if>
方案2.增加对0的判断:
<if test="billMoney != null and billMoney !='' or billMoney == 0 ">
and bill_money = #{billMoney}
</if>
这两种方式都可以解决问题,最终实现的效果也是一样的,方案1相对来说代码更为简洁一些。
解决问题看到这儿就够了,想知其所以然的小伙伴可以继续向下看。那为什么会出现这个问题呢,就必须去查看mybatis的源码实现了。
我们通过debug进入mybatis代码层,首先进入的是MapperProxy类,调用了invoke()方法来对应mapper里的方法。接着调用了getResulSets类的getBoundSql()方法,来获取具体的执行sql。在MixedSqlNode类中进行sql的拼接。
@Override
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
一步步追踪下去,sqlNode是否要拼接到执行sql上,最终是通过OgnlOps类的equal()方法来判断的,这也是我们研究的重点
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;
}
}
这个方法是用来判断<if test="billMoney != null and billMoney !=''">and bill_money = #{billMoney}</if>最终的布尔值的,此时传入值为0.0,当比较值为null的时候,billMoney != null是返回了true,而billMoney !=''最终返回了false,也就是说在这次判断中,最终认为0.0是等于''的!那么为什么会出现这种情况呢,其实是经过一系列的方法调用,最后达成这个结果的。大致过程是equal()->isEqual()->compareWithConversion()->doubleValue()。由于都是存在于OgnlOps类中,这里我标出了两步关键代码。
当字段类型为Double时,最终会走到case8这步判断,而在doubleValue()方法中,会将空字符串''解析为0.0。导致isEqual()方法最终返回的结果是true,进一步equal()结果返回ture,这也是0.0 !=''最终返回false的原因。
当然,可以看出,mybatis并不是把传入的0.0解析成了空字符串,而是把mapper.xml里的空字符串解析成了0.0。
扩展:都有哪些数据类型,最终会走到case8?
先把结论写上,后续有时间再补充详细内容。8种基本类型以及包装类型,再加上BigInteger和BigDecimal,最终都会走到case8,这些类型都不能使用0!=''这样的判断。