最近在写 MyBatis 的 mapper.xml 时遇到一个坑,记录一下。
问题是这样的:有个查询条件是状态字段(Integer 类型),当状态为 0 时,这个条件居然没生效。
排查了半天,发现问题出在 <if> 标签的判空逻辑上。
问题复现
mapper.xml 中的写法:
<select id="selectList" resultType="UserDO">
SELECT * FROM user
<where>
<if test="status != null and status != ''">
AND status = #{status}
</if>
</where>
</select>调用时传入 status = 0,结果这个条件被忽略了,SQL 中没有 AND status = 0。
原因分析
MyBatis 使用 OGNL 表达式,在 OGNL 中:
0 == '' // 结果是 true所以当 status = 0 时:
status != null and status != ''
// 等价于
true and (0 != '')
// 等价于
true and false
// 结果是 false条件不成立,<if> 标签里的内容就被跳过了。
解决方案
方案一:只判断非空(推荐)
<if test="status != null">
AND status = #{status}
</if>Integer 类型的字段,只需要判断 != null 就行了,不需要判断空字符串。
方案二:增加数值判断
<if test="status != null and status != '' and status >= 0">
AND status = #{status}
</if>这样写也可以,但有点多余,不如方案一简洁。
方案三:统一用 1 作为默认值
有些团队会约定 Integer 类型字段不用 0,用 1 表示默认状态。
// 状态:1-启用,2-禁用
private Integer status;这样就不会有 0 的问题了,但这是规避问题,不是解决问题。
其他注意事项
字符串字段的判断
字符串类型的字段,判空时需要同时判断 null 和空字符串:
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>List 判断
判断集合是否为空:
<if test="ids != null and ids.size() > 0">
AND id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>总结
这个坑很经典,但很容易踩:
- Integer 类型字段判空时,只判断
!= null即可 - 不要加
and xxx != ''的判断,否则 0 会被当成空值 - 字符串类型才需要同时判断 null 和空字符串
虽然是个小问题,但排查起来挺费时间的,记录一下以后避免再踩\~
扩展阅读
除了这个坑,MyBatis 中还有一些常见问题:
1. 参数为空时全表查询
<!-- 错误写法 -->
<if test="name != null and name != ''">
AND name = #{name} <!-- 只有 name 为空时不查,否则全表查 -->
</if>应该明确查询条件,避免参数为空时无条件查询全表。
2. <where> 标签的使用
<select id="selectList" resultType="UserDO">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select><where> 标签会自动处理 AND 前缀,避免 SQL 语法错误。
3. $ 和 # 的区别
<!-- #{} 是预编译,安全 -->
AND name = #{name}
<!-- ${} 是字符串拼接,有 SQL 注入风险 -->
ORDER BY ${orderBy}能用 #{} 就用 #{},${} 只在表名、字段名等不能预编译的场景使用。
这些细节在写 MyBatis 时都要注意,很容易踩坑\~