记一次MyBatis解析的SQL和实际传参不符的问题

起源于一个“诡异”的Bug

愉快的周末,正在吃饭,忽然微信响起,看见同事群有同事在问问题,大致是:

Mybatis传的参数类型是Integer,值是3,但是生成的SQL却解析成Boolean类型的true

看到这个问题,第一反应就是哪里配置出问题了,但由于今天要发版,测试同事正在加班,立马就认为这个问题的处理思路应该是先解决再探究,于是凭借经验在群里给同事支招。

同事之间讨论

最直接的解决方案就是,既然类型错了就强制指定类型呗,于是支招强制指定javaType,可同事说她已经试过了,不得行。
此时其他同事们也纷纷表示这个问题有点奇怪,大致提了几个猜想:
有建议升级Mybatis版本的,言外之意就是怀疑当前版本有bug。直觉告诉我不应该是这个问题,因为这类使用广泛的开源项目,不说没有Bug,但至少bug应该不会这么明显,这么严重;
也有说这个可能是Mybatis(MySQL)关键字的,被处理成特殊逻辑了。经验告诉我这不可能是MySQL关键字,首先MySQL关键字有哪些大致是清楚的,退一万步就算自己记错了,如果是MySQL关键字,不应该是解析代码出问题,而是发送到MySQL执行SQL时报错。其次是Mybatis关键字,一个ORM框架没事是不会搞什么“关键字”的。
于是我又提了一个建议,把类型改成String试试。因为我怀疑是Mybatis因为各种原因把Integer处理成Boolean了(因为之前遇到过定义成tinyint(1)时,Mybatis-plus生成的映射类型是布尔),刚好整型3的低bit位又是1,所以刚好是true。很遗憾,同事说她也试过了,她折腾了一下午,各种情况都试过了,始终无法解决。
这下我就来劲了,于是我回复这种情况只能调试了,然后三下五除二就把饭吃完来到电脑前,准备开撸。
问了下是哪个模块,然后快速定位到问题代码,伪代码形如:

//ADao
void search(@Param("paramA") Integer a, @Param("paramB") Integer b);

//AMapper
<select id="search" resultType="int">
...
<if test="paramA != null and paramsA > 1">
  AND param_a = #{paramA}
</if>
...
<if test="paramA = 1 and paramsB != null and paramB > 3">
  ...
</if>
...
</select>

就是参数a,传值是3(debug也确实为3),但在解析成SQL时生成的片段为

AND param_a = true

看到这里,仔细的同学应该已经能发现问题了,但是这段代码由于是只截取出了出问题的,实际代码看起来也是不太容易发现的,再加上当时一心只想着调试找问题,并没想过静下来看代码找问题。此时同事说她改个名字就能解决改问题,更是为这个问题蒙受一层神秘的色彩。

解决问题

由于同事已经修改了名称且提交了代码,我简单修改成之前的,快速运行一下,直接好家伙,果然是这样!
在这里插入图片描述
在这里插入图片描述
于是开始调试,发现走到DynamicSqlSource.getBoundSql的时候,就已经出现异常了,alarmCategory字段被解析成了Boolean
在这里插入图片描述
于是锁定问题就出现在上面的几行代码里,简单调试后确定问题出在apply方法中,再进一步缩小范围后
在这里插入图片描述
问题出在OgnlCache.getValue,这行代码执行后parameterObject就会多一个元素,key就刚好是alarmCategory,值是true。后面在解析parameterMappings的时候就自然解析成Boolean了(因为解析逻辑是从自顶向下的),再进一步看为什么不是解析成String、Integer偏偏是Boolean呢?
在这里插入图片描述
Mybatis的Ognl表达式把这段代码解析成类似paramA = (1 && (paramB != null) && (paramB > 3)) 这样的抽象语法树。
此时就明朗了,后面的代码已经不重要了,这个代码片断,字面意思就是后面有个布尔表达式,计算出它的结果,再赋值给前面的变量即可。
赋值语句在布尔表达式里是真是假?真!所以这个if表达式永远是真,就会产生bug。(而这次同事就是为了解决因为这里永远为真导致的一个bug,但当时没发现是这里引起的,反而去执着解决解析异常的问题)

事后总结

发这个不是为了说这个问题多么高深,旨在说明几个道理(强行总结):
1、解决问题的最快方式当然是靠经验,遇到过问题再次遇到自然是轻车熟路,但是遇到陌生的甚至“诡异”的问题时,RTFC肯定是最好的途径!
2、但凡能称之为“诡异的问题”的,大多是粗心导致的,多检查检查代码。
3、吃饭时不要玩手机[doge]

担心有同学看完后一脸懵,那怎么解决?少一个等号,增加一个即可,即伪代码中的paramA = 1 and paramsB != null and paramB > 3改为paramA == 1 and paramsB != null and paramB > 3

有奖问答

若是上述伪代码稍加修改

//ADao
void search(@Param("paramA") Integer a, @Param("paramB") Integer b);

//AMapper
<select id="search" resultType="int">
...
<if test="paramA != null and paramsA > 1">
  AND param_a = #{paramA}
</if>
...
<if test="paramA = 1">
  ...
</if>
...
</select>

and paramsB != null and paramB > 3去掉了会怎样?
在这里插入图片描述

欢迎(求)转载,注明出处即可!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值