前言:
米娜,今天的文章还是简确用的文章,希望可以帮到你们。
Mybatis 有一些标签,用来支持动态 sql 语句,简单来说,这些标签可以控制 sql 语句的输出,设置某些条件来让Mapper输出不同的 sql 语句,今天这篇文章主要说一下使用<if>标签会遇到的坑。
正文:
一、复现问题
1.数据库的数据
2.Controller层代码
@RestController
@RequestMapping("/study")
public class StudentController {
@Autowired
StudentService studentService;
@RequestMapping(value = "/selectStudent", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public String selectStudent(Integer sex){
JSONObject result = new JSONObject();
List<Student> students = studentService.selectStudent(sex);
result.put("data",students);
return result.toString();
}
}
3.Dao层代码
public interface StudentMapper extends BaseMapper<Student> {
List<Student> selectBySex(@Param("sex") Integer sex);
}
4.xml文件的代码
<select id="selectBySex" resultMap="BaseResultMap">
select * from student where 1=1
<if test="sex != null and sex != '' ">
and sex = #{sex}
</if>
</select>
5.postman请求的参数
根据上面的代码和数据库已有的数据,我们猜测sex传1的时候,应该有一条数据,sex传0的时候有两条数据,不传的时候有三条数据。我们现在看下结果:
sex=1 ,和我们猜想的一样,1条数据
sex=0 ,出现了三条数据和我们的猜想不一样,问题复现出来了,这时候出现三条数据,说明<if>标签没有起作用
sex传null的时候,和我们猜想的一样,3条数据
二、分析问题
1.我们先看下控制台打印sql的区别
sex=1
select * from student where 1=1 and sex = ?
sex=null
select * from student where 1=1
sex=0
select * from student where 1=1
可以明显看出来参数传0的时候,没有走<if>标签里的条件,test判断的似乎把等于0的情况,也当做false,所以没有走if里面的部分。
<if test="sex != null and sex != '' ">
and sex = #{sex}
</if>
2.那为什么会造成sex=0 返回的是false,这得稍微看下MyBatis中一些关于动态SQL的接口和类,先看一张类图
我们可以看到关于if的标签设计到的类是IfSqlNode,那我们就先看下这个类
public class IfSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator;
private final String test;
private final SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
public boolean apply(DynamicContext context) {
if (this.evaluator.evaluateBoolean(this.test, context.getBindings())) {
this.contents.apply(context);
return true;
} else {
return false;
}
}
}
我们发现IfSqlNode的apply方法这里进行了判断,于是推测是否在这里进行条件的判断,我们再进一步看下evaluateBoolean方法
public boolean evaluateBoolean(String expression, Object parameterObject) {
Object value = OgnlCache.getValue(expression, parameterObject);
if (value instanceof Boolean) {
return (Boolean)value;
} else if (value instanceof Number) {
return (new BigDecimal(String.valueOf(value))).compareTo(BigDecimal.ZERO) != 0;
} else {
return value != null;
}
}
通过断点发现value的时候就是false啦,所以看下getValue方法
然后在不断往下追溯源码后,找到方法compareWithConversion,通过断点我们可以到,0和“”最终都会转化成0.0,所以在mybatis的if标签里0其实等价于“”,这也是为什么mybati中if test 0!=""判定为false的原因。
三、解决问题
1.在建表的时候如果某个字段定义成int类型,建议可以用1为基础往上加,这样就可以避免使用0来当参数筛选条件了,从根源上制止。
举个例子,sex可以用1表示男 2表示女 3表示未知
2.如果任性就是想用0来表示,可以在<if>标签这样写 or sex==0
<select id="selectBySex" resultMap="BaseResultMap">
select * from student where 1=1
<if test="sex != null and sex != '' or sex==0 ">
and sex = #{sex}
</if>
</select>
然后点击postman测试一下,数据变成两条了
再看下打印的日志:
=> Preparing: select * from student where 1=1 and sex = ?
==> Parameters: 0(Integer)
<== Columns: id, name, age, sex
<== Row: 1, dada, 18, 0
<== Row: 3, dd, 18, 0
<== Total: 2
发现sex=0的条件起作用了,证明这种方式也可以的。
总结:
持续有节奏的学习,希望大家都可以早早退休!最近有首歌很好听,安利给小伙们,《海底》下班的路上,可以听听。
我是阿达,一名喜欢分享知识的程序员,时不时的也会荒腔走板的聊一聊电影、电视剧、音乐、漫画,这里已经有9777位小伙伴在等你们啦,感兴趣的就赶紧来点击关注我把,哪里有不明白或有不同观点的地方欢迎留言!