页面组件多条件模糊查询

476 阅读5分钟

这是多条件模糊搜索的前端界面。


可以看到,条件有五个。如果某项不选,就忽略这一项;五项都选,就查询同时满足这五项的数据。

  • 姓名:搜索姓名中包含某个字符串的数据
  • 订单状态:搜索订单状态相同于多个状态其中一个的数据(下拉选择框中是多选)
  • 开始时间:搜索创建时间晚于某个时间的数据
  • 截止时间:搜索创建时间早于某个时间的数据
  • 地址:搜索地址中包含某个字符串的数据

数据类型

  1. 后台从搜索框里得到的 姓名地址开始与截止时间 都是 String 类型,
  2. 订单状态List[String, ...,String] 类型,因为下拉选择框得到状态码的就是String类型。

操作类型

  1. 姓名和地址是模糊查询
  2. 订单状态需要再sql语句中循环列表做等价查询,or 连接
  3. 开始和截止时间是范围查询,如果开始时间和截止时间都有,就搜索处于其间的


我们知道,对于多条件模糊搜索,空值的处理是重点。这部分可以在前端就处理完,也可以在后端处理,也可以传到sql语句中再处理。我比较倾向于在后端规定好sql语句中需要的数据类型后,统统装载到一个HashMap里,把它送到sql中去处理,传参很方便,同时mybatis的sql语句支持 ifforeach 等操作,非常友好。



一、Controller层代码

@RequestMapping(value = "/search", method = RequestMethod.POST)
public String queryFuzzy(@RequestParam("s_name") String s_name,
                            @RequestParam(value = "s_order_status" ,required = false) List s_order_status,   // 如果不加 required=false ,当为空时会报错
                            @RequestParam("s_startTime") String s_startTime,
                            @RequestParam("s_endTime") String s_endTime,
                            @RequestParam("s_address") String s_address,

                            @RequestParam(value = "page", defaultValue = "1") int page,
                            Model model
                            ) throws ParseException {


    // 处理可选的状态(List[String]类型 ————>  List[String]类型)
    ArrayList<Integer> codesInteger = new ArrayList<>();   // 这里不能初始化为null,否则下面的null.add()会报NLP的错误
    if(s_order_status!=null && s_order_status.size()>0){  // 如果不先加null的判断,后面的null.size()会报NLP的错误
        for(Object s : s_order_status) codesInteger.add(Integer.valueOf(String.valueOf(s)));  // 将List <String>转换为List <Integer>  因为数据库中的状态码为Tinyint型,不能拿String去比较  
    }



    // 处理日期(String类型 ————>  Date类型)
    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date startTime = null,endTime = null;   // 处理空值
    if(s_startTime!=null && s_startTime!=""){
        startTime = formatter.parse(s_startTime);
    }
    if(s_endTime!=null && s_endTime!=""){
        endTime = formatter.parse(s_endTime);
    }



    HashMap<String,Object> queryMap = new HashMap<>();  // key为String,value需要是为Object
    queryMap.put("s_name",s_name);
    queryMap.put("s_order_status",codesInteger);
    queryMap.put("s_startTime",startTime);
    queryMap.put("s_endTime",endTime);
    queryMap.put("s_address",s_address);



    List<Order> list = orderService.queryFuzzy(queryMap);
    PageHelper.startPage(page, list.size());    // 不再分页,用一页来装完
    model.addAttribute("msg", "查到了"+list.size()+"条信息");
    model.addAttribute("pageInfo", new PageInfo<>(list));


    return "views/customer/manage";

}

代码看起来比较多,但逻辑上很简单。首先,我前端数据是在bootstrap模态框中用form表单提交的,并且字段名和数据库中的不一致(因为用到了模态框,字段名要和主页面上的字段名区分开),所以我后端用@RequestParam 单个接收,毕竟查询条件也不多。需要注意的是,接收List列表时,设置了 required = false ,表示不对他做表单验证,这是必需的,否则会报错。


然后开始处理数据类型,首先就是List列表,列表中的每个数据都是字符串类型的状态码,不能拿它去和数据库中Integer类型的状态码去比较,所以需要把 List[String] 类型转换为 List[Integer] 类型。Java8中其实有一个现成的方法:

stringList.stream().map(Integer::parseInt).collect(Collectors.toList());

但我这里 parseInt 下面一直有红线,原因未知,遂放弃,改用了最笨的遍历方法。需要注意一下NPL的错误,在注释里有就不多说了。


另外就是日期类型,sql语句中日期比较只支持Date。而从前端接收到的是String类型。所以需要转换一下,同样需要注意烦人的NPL问题,也在注释里。


然后开辟一个 HashMap<String,Object>,去把所有处理好的查询条件put进去,拿它去经过Service层和Dao层到mapper里查询,得到查询结果list配合PageHelper返回给前端展示。



二、Mapper中的sql语句

重要的就是mapper里的sql语句。

<select id="queryFuzzy" parameterType="java.util.Map" resultType="cn.hsy.pojo.Order">
    select * from my_order where 1=1
    
    <if test="s_name!=null and s_name!=''">and name like concat('%',#{s_name},'%')</if>
    <if test="s_address!=null and s_address!=''">and address like concat('%',#{s_address},'%')</if>

    <if test="s_startTime!=null ">and create_time &gt; #{s_startTime}</if>
    <if test="s_endTime!=null ">and create_time &lt; #{s_endTime}</if>

    <if test="s_order_status!=null and s_order_status.size()>0">and (
        <foreach collection="s_order_status" item="each" index="idx">
            order_status=#{each}
            <if test="idx != (s_order_status.size() - 1)">
                or
            </if>
        </foreach>
        )

    </if>


</select>

分开来说,首先是

select * from my_order where 1=1

其中的 where 1=1 很重要,它是为了防止在无任何条件时,sql语句出错。具体来说:如果没有where 1=1 , 只在后面用 if 堆积判断条件,如果所有的 if 都不成立,那就相当于没有任何判断条件,上述sql语句就变成了select * from my_order where,可以看出where后的条件缺失,会导致sql语句报错。另外,where 1=1 是恒成立的,不会影响其它条件。


下面是姓名和地址的模糊查询

<if test="s_name!=null and s_name!=''">and name like concat('%',#{s_name},'%')</if>
<if test="s_address!=null and s_address!=''">and address like concat('%',#{s_address},'%')</if>

if test="" 是MyBatis中的判断语句,当test为true时,if标签内的语句会被执行,反之,则会被忽略。而 and 的作用是拼接多个判断语句,这个 and 就是把当前判断语句和前面的where 1=1 拼接起来。

name like concat('%',#{s_name},'%') 是模糊查询必需的写法,不能写为 name like {s_name}


下面是时间的范围查询,

<if test="s_startTime!=null ">and create_time &gt; #{s_startTime}</if>
<if test="s_endTime!=null ">and create_time &lt; #{s_endTime}</if>

同样用到了 if 来判空。比较早晚直接用小于大于号即可,但是在xml文件中,所有文本均会被解析器解析,使用 <>& 符号是非法的,会被解析导致报错。为了避免错误,就需要就这些字符替换成实体引用。在xml中有5个预定义的实体引用:

<<小于号
>大于号
&&和号
'单引号
"双引号

最后是状态的列表遍历等价查询。

<if test="s_order_status!=null and s_order_status.size()>0">and (
    <foreach collection="s_order_status" item="each" index="idx">
        order_status=#{each}
        <if test="idx != (s_order_status.size() - 1)">
            or
        </if>
    </foreach>
    )
</if>

MyBatis支持 foreach 来实现遍历,在非空的情况下,先用 and 与上一个查询条件拼接。判断列表中的每一项时要用 or 拼接,虽说sql语句中 and 优先级已经比 or 要高了,但是为了可读性,我还是加了一对括号。

另外要注意查询完列表中最后一个元素后就不能再用 or 了,所以不能写成 order_status=#{each} or ,而要把 or 单拿出来,foreach 恰好也支持下标操作,就用 idx != (s_order_status.size() - 1) 来判断当前元素是否为最后一个元素,如果是,就不再加 or 了。


完毕。