Mybatis的「if」 标签有坑之参数是0的时候会被过滤掉!

前言:

米娜,今天的文章还是简确用的文章,希望可以帮到你们。

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位小伙伴在等你们啦,感兴趣的就赶紧来点击关注我把,哪里有不明白或有不同观点的地方欢迎留言!

猜你喜欢

转载自blog.csdn.net/jdk_wangtaida/article/details/107226622