复盘
近期有一个小业务需求,非常简单,本质就是增加几个过滤参数,可以透传给数据库就可以了。
代码使用MyBatis作为ORM框架
原始SQL如下
<select id="listDevices" resultType="DeviceDO">select device_uuid as deviceUUID, client_type as clientTypefrom tb_deviceorder by gmt_modified desc
</select>
上面没有过滤条件,本次改造是增加client_type的过滤,由于可能存在多个client_type,故需要使用MyBatis的foreach用法构造in条件。当时偷了个懒,直接从现有代码库里面copy了一份做了改动。
第一版改造如下:
<select id="listDevices" resultType="DeviceDO">select device_uuid as deviceUUID, client_type as clientTypefrom tb_devicewhere client_type in <foreach collection="list" index="index" item="item" open="(" close=")" separator=",">${item}</foreach>order by gmt_modified desc
</select>
关于foreach用法可以参照:https://juejin.cn/post/6844903886575108110
自以为应该没有问题,就屁颠屁颠的部署服务到预发环境了,结果报错了。
MyBatis的异常:
Unknown column 'android' in 'where clause'] with root cause
奇怪,怎么会把android当做了列呢,这明明是查询条件才对。从日志里面又捞取了一下执行的SQL,终于发现端倪,原来传入到的条件并没有添加""。
select device_uuid as deviceUUID, client_type as clientType
from tb_device
where client_type in (android , iOS )
order by gmt_modified desc
那就再检查一下代码吧,把自己的代码和原来copy的代码仔细做了一下对比,并未发现异常,奇怪+1。
那就再检查一下对应的字段类型吧,发现原来正常工作的类型是int,所以即使不加引号也可以正常运行。但是这里的client_type是varchar类型,如果不加引号那就会报错。
所以问题来了,为什么没有添加引号呢? 还是检查一下MyBatis的Mapper文件,发现犯了一个巨大的错误,其他条件地方使用的是#+变量名,这里使用了$+变量名,那就替换一下吧。
更新后SQL:
<select id="listDevices" resultType="DeviceDO">select device_uuid as deviceUUID, client_type as clientTypefrom tb_devicewhere client_type in <foreach collection="list" index="index" item="item" open="(" close=")" separator=",">#{item}</foreach>order by gmt_modified desc
</select>
替换以后执行就正常了。
==> Preparing: select device_uuid as deviceUUID, client_type as clientType from tb_device where client_type in ( ? , ? ) order by gmt_modified desc
==> Parameters: android(String), iOS(String)
<== Total: 0
延伸
关于MyBatis #和$的区别,可以参照一下这个https://segmentfault.com/a/1190000004617028
提取一下文章关键信息,mybatis 在对 sql 语句进行预编译之前,会对 sql 进行动态解析。
- #{ } 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,能使用#{ }的地方就用#{ }
- ${ } 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换,表名作为变量时,必须使用 ${ }。