在利用SpringBoot集成MyBatis实现简单的业务操作时,遇到了一个坑,记录一下。
一般来讲,实现多条件模糊查询的逻辑为:前端页面输入各项参数,提交查询请求时,参数通过RUL(或者请求体)传输到后端。后端提取出相关参数,注入至sql语句,实现多条件模糊查询。
Service层相关代码(本文省略Controller层代码)
/**
* 分页查询(基于此实现多条件模糊查询)
*/
@Override
public PageInfo<ProductInfo> splitPageForPageInfo(int pageNum, int pageSize, ProductInfo productInfo) {
PageHelper.startPage(pageNum, pageSize);
List<ProductInfo> productInfoList = productDao.selectAll(productInfo);
//PageHelper相关配置已经在yml文件中声明,项目加载时会生成bean放入到Spring容器中
//原sql语句后面会自动添加Limit
PageInfo<ProductInfo> pageInfo = new PageInfo<>(productInfoList);
return pageInfo;
}
刚开始本人的Dao层代码如下所示
@Select("<script>"
+"select * from product_info"
+ "<where>"
+ "<if test='typeId !=null'> and type_id like CONCAT('%',#{typeId},'%')</if>"
+ "<if test='pName !=null'> and p_name like CONCAT('%',#{pName},'%')</if>"
+ "<if test='pContent !=null'> and p_content like CONCAT('%',#{pContent},'%')</if>"
+ "<if test='pNumber !=null'> and p_number like CONCAT('%',#{pNumber},'%')</if>"
+ "<if test='pPrice !=null'> and p_price like CONCAT('%',#{pPrice},'%')</if>"
+ "</where>"
+"</script>"
)
List<ProductInfo> selectAll(ProductInfo productInfo);
注意到此处的<if>标签中,只包含了各个属性为null的情况,而忽略了它们为空串的情形,这个小细节将会导致后续页面无法正常显示数据,并且我们很难查出错误所在。比如本人在测试类中调试Service层splitPageForPageInfo()方法,代码与运行结果如下:
@Test
void testSplitPageForPageInfo() {
ProductInfo productInfo = new ProductInfo();
productInfo.setTypeId("电脑");
PageInfo pageInfo = productService.splitPageForPageInfo(1, 3, productInfo);
List<ProductInfo> productInfoList = pageInfo.getList();
for (ProductInfo p : productInfoList) {
System.out.println(p);
}
}
2021-12-30 09:30:23.273 DEBUG 11764 --- [ main] c.wangjf.dao.ProductDao.selectAll_COUNT : ==> Preparing: SELECT count(0) FROM product_info WHERE type_id LIKE CONCAT('%', ?, '%')
2021-12-30 09:30:23.299 DEBUG 11764 --- [ main] c.wangjf.dao.ProductDao.selectAll_COUNT : ==> Parameters: 电脑(String)
2021-12-30 09:30:23.319 DEBUG 11764 --- [ main] c.wangjf.dao.ProductDao.selectAll_COUNT : <== Total: 1
2021-12-30 09:30:23.322 DEBUG 11764 --- [ main] com.wangjf.dao.ProductDao.selectAll : ==> Preparing: select * from product_info WHERE type_id like CONCAT('%',?,'%') LIMIT ?
2021-12-30 09:30:23.323 DEBUG 11764 --- [ main] com.wangjf.dao.ProductDao.selectAll : ==> Parameters: 电脑(String), 3(Integer)
2021-12-30 09:30:23.326 DEBUG 11764 --- [ main] com.wangjf.dao.ProductDao.selectAll : <== Total: 2
ProductInfo(pId=4, pName=联想小新, pContent=dddd, pPrice=8000.0, pNumber=1, typeId=电脑)
ProductInfo(pId=13, pName=外星人, pContent=水, pPrice=10999.0, pNumber=1, typeId=电脑)
通过MyBatis日志输出可以看到,此代码逻辑下sql语句的运行顺序为:首先查询出type_id字段中含有''电脑"这个字符串的所有数据(大家自动忽略此处的数据名称和内容...此表是俺从上一个项目中随便捡来的...),再利用Limit语句分页。可以看到查询结果是正确的
而当我们将productInfo对象赋为null,理论上sql语句执行顺序为:因无条件匹配,直接在原sql语句后面加上Limit实现分页。从运行日志可以看出执行顺序也确实如此:
@Test
void testSplitPageForPageInfo() {
//ProductInfo productInfo = new ProductInfo();
//productInfo.setTypeId("电脑");
PageInfo pageInfo = productService.splitPageForPageInfo(1, 3,null);
List<ProductInfo> productInfoList = pageInfo.getList();
for (ProductInfo p : productInfoList) {
System.out.println(p);
}
}
2021-12-30 09:31:56.297 DEBUG 6404 --- [ main] c.wangjf.dao.ProductDao.selectAll_COUNT : ==> Preparing: SELECT count(0) FROM product_info
2021-12-30 09:31:56.324 DEBUG 6404 --- [ main] c.wangjf.dao.ProductDao.selectAll_COUNT : ==> Parameters:
2021-12-30 09:31:56.341 DEBUG 6404 --- [ main] c.wangjf.dao.ProductDao.selectAll_COUNT : <== Total: 1
2021-12-30 09:31:56.343 DEBUG 6404 --- [ main] com.wangjf.dao.ProductDao.selectAll : ==> Preparing: select * from product_info LIMIT ?
2021-12-30 09:31:56.344 DEBUG 6404 --- [ main] com.wangjf.dao.ProductDao.selectAll : ==> Parameters: 3(Integer)
2021-12-30 09:31:56.346 DEBUG 6404 --- [ main] com.wangjf.dao.ProductDao.selectAll : <== Total: 3
ProductInfo(pId=1, pName=iphone14, pContent=128GB, pPrice=5999.0, pNumber=1, typeId=2)
ProductInfo(pId=2, pName=iphone15, pContent=256GB, pPrice=6799.0, pNumber=2, typeId=1)
ProductInfo(pId=3, pName=专用座机, pContent=null, pPrice=null, pNumber=null, typeId=手机)
为确保万无一失,本人还用Postman进行了测试
1.查询全部
2.按照pageNum=1,pageSize=3分页查询
3.基于条件typeId="电脑"进行模糊查询
上面三项测试都得到正确的结果.. 此时我感觉已经大功告成了!
但是当我将项目运行-->打开网页--->刷新.. 竟然GG了..
乍一看,似乎没啥问题,但是仔细一看.. 竟然漏掉了字段属性存在空值的一条数据(通过控制台打印的消息也可以看到total=6)
怎么可以容忍这样的事情发生?!
经过漫长的寻觅.. (不再赘述本人的痛苦遭遇),终于发现原因为:在<if>标签中没有考虑数据为空串的情况.... 修改Dao层代码如下:
@Select("<script>"
+"select * from product_info"
+ "<where>"
+ "<if test='typeId !=null and typeId != \"\"'> and type_id like CONCAT('%',#{typeId},'%')</if>"
+ "<if test='pName !=null and pName != \"\"'> and p_name like CONCAT('%',#{pName},'%')</if>"
+ "<if test='pContent !=null and pContent != \"\"'> and p_content like CONCAT('%',#{pContent},'%')</if>"
+ "<if test='pNumber !=null and pNumber != \"\"'> and p_number like CONCAT('%',#{pNumber},'%')</if>"
+ "<if test='pPrice !=null and pPrice != \"\"'> and p_price like CONCAT('%',#{pPrice},'%')</if>"
+ "</where>"
+"</script>"
)
List<ProductInfo> selectAll(ProductInfo productInfo);
最终可得正确网页显示(控制台输出total=7):
多条件模糊查询功能也正常
谨以本人第一篇CSDN来缅怀那逝去的一个夜晚.....
注:查询错误的具体原因分析
当我们没有在<if>标签中添加"空串"条件时,刷新网页,得到日志信息如下:
2021-12-30 10:44:03.460 DEBUG 18176 --- [nio-8080-exec-2] c.wangjf.dao.ProductDao.selectAll_COUNT : ==> Preparing: SELECT count(0) FROM product_info WHERE type_id LIKE CONCAT('%', ?, '%') AND p_name LIKE CONCAT('%', ?, '%') AND p_content LIKE CONCAT('%', ?, '%')
2021-12-30 10:44:03.476 DEBUG 18176 --- [nio-8080-exec-2] c.wangjf.dao.ProductDao.selectAll_COUNT : ==> Parameters: (String), (String), (String)
2021-12-30 10:44:03.489 DEBUG 18176 --- [nio-8080-exec-2] c.wangjf.dao.ProductDao.selectAll_COUNT : <== Total: 1
2021-12-30 10:44:03.492 DEBUG 18176 --- [nio-8080-exec-2] com.wangjf.dao.ProductDao.selectAll : ==> Preparing: select * from product_info WHERE type_id like CONCAT('%',?,'%') and p_name like CONCAT('%',?,'%') and p_content like CONCAT('%',?,'%') LIMIT ?
2021-12-30 10:44:03.492 DEBUG 18176 --- [nio-8080-exec-2] com.wangjf.dao.ProductDao.selectAll : ==> Parameters: (String), (String), (String), 3(Integer)
2021-12-30 10:44:03.495 DEBUG 18176 --- [nio-8080-exec-2] com.wangjf.dao.ProductDao.selectAll : <== Total: 3
我们可以看到整条sql语句是在<where>标签下的,众所周知,只有<where>标签内的条件成立,才会在拼接sql时加入WHERE关键字。所以日志结果表明,当前有条件满足了。并且我们很容易知道是以下三个条件满足了(也就是说typeId、pName、pContent这三个数据不为null)
<if test='typeId !=null'>
<if test='pName !=null'>
<if test='pContent !=null'>
这样拼接得到的sql语句的查询结果就是6条数据..
这就奇怪了?单纯地刷新页面,理论上这些参数的值应该都为null啊?
既然测试类与Postman都运行正常,那只有是前端页面的问题了...
于是我发现在html文件中,当时为了方便,将这些属性的初始值都设为空串了(当然这边是无法直接将属性赋为null的,其会被当成"null"字符串传入),而我又理所当然地认为"空串"=null
typeId: "",
pName: "",
pContent: "",
pNumber: "",
pPrice: ""
可为什么以下这两个条件还是被判定为不成立呢?
<if test='pNumber !=null'>
<if test='pPrice !=null'>
由此个人认为:若在实体类中定义为数值类型的属性(Integer、Double、Float),其被赋默认值为"空串"时,MyBatis底层判断为null,而在实体类中定义为字符类型的属性(String、Char),其被赋默认值为"空串"时,MyBatis底层判断为非null。这点欢迎大家去亲自实践验证...