Lecture05-条件构造器[一]-QueryWrapper

文章目录

    [toc]

    单词词性音标含义
    Queryv./ˈkwɪəri/查询(QueryWrapper 是封装查询条件的包装器对象)
    Wrappern./ˈræpə(r)/包装器(把多个查询条件包装成一个对象传给 Mapper 方法)
    Betweenprep./bɪˈtwiːn/在…之间(between 方法生成 BETWEEN ? AND ? 区间条件)
    Likeprep./laɪk/像(like 方法生成模糊查询 LIKE '%值%')
    Ordern./ˈɔːdə(r)/顺序(orderByDesc 按指定列降序排序)

    Lecture05-条件构造器[一]-QueryWrapper

    一、为什么需要条件构造器

    第 4 章的查询方法传 null 表示不带条件,但实际开发中查询往往带各种条件:

    • 按姓名模糊查、按工资区间查、按部门查、按状态查……
    • 条件还可能组合、动态变化(前端传什么条件就查什么)

    如果每个条件都写一条 SQL,条件一多就爆炸了。MyBatis-Plus 的解决方案:把条件封装成 Wrapper(条件构造器)对象,用方法链拼接条件,交给 Mapper 方法执行。


    二、入门示例

    需求:查询部门 1 中、姓名包含「张」的员工,按工资降序

    需要导入:import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

    @Test
    public void testQueryWrapper() {
        QueryWrapper<Employee> wrapper = new QueryWrapper<>();
    
        wrapper.eq("dept_id", 1)                    // 部门 = 1
               .like("emp_name", "张")              // 姓名包含「张」
               .orderByDesc("emp_salary");          // 按工资降序
    
        List<Employee> employeeList = employeeMapper.selectList(wrapper);
        employeeList.forEach(System.out::println);
    }
    

    控制台生成的 SQL:

    ==>  Preparing: SELECT emp_id,emp_no,emp_name,emp_salary,emp_birthday,phone_num,dept_id FROM t_emp 
                    WHERE dept_id=? AND emp_name LIKE ? 
                    ORDER BY emp_salary DESC
    ==> Parameters: 1(Integer), %张%(String)
    

    观察:

    • 多个条件之间自动用 AND 连接
    • like("emp_name", "张") 自动变成 LIKE '%张%'
    • 参数值用 ? 占位,防止 SQL 注入

    三、图解:条件构造器如何变成 SQL

    QueryWrapper 对象到最终 SQL 的转换

    要点:

    • 每调用一个方法,就往 Wrapper 里追加一段条件
    • 框架最终把这些条件片段按顺序拼装成完整 SQL
    • Wrapper 里写的列名是数据库列名(下划线),不是实体属性名

    四、常用方法速查表

    方法生成的条件示例
    eq(列, 值)列 = 值eq("dept_id", 1)
    ne(列, 值)列 <> 值ne("dept_id", 1)
    gt(列, 值)列 > 值gt("emp_salary", 8000)
    ge(列, 值)列 >= 值ge("emp_salary", 8000)
    lt(列, 值)列 < 值lt("emp_salary", 10000)
    le(列, 值)列 <= 值le("emp_salary", 10000)
    between(列, 值1, 值2)列 BETWEEN 值1 AND 值2between("emp_salary", 5000, 10000)
    notBetween(列, 值1, 值2)列 NOT BETWEEN 值1 AND 值2notBetween(...)
    in(列, 值1, 值2, ...)列 IN (值1, 值2, ...)in("dept_id", 1, 2)
    notIn(列, 集合)列 NOT IN (...)notIn("dept_id", Arrays.asList(1))
    like(列, 值)列 LIKE '%值%'like("emp_name", "张")
    notLike(列, 值)列 NOT LIKE '%值%'notLike("emp_name", "张")
    likeLeft(列, 值)列 LIKE '%值'likeLeft("emp_name", "张")
    likeRight(列, 值)列 LIKE '值%'likeRight("emp_name", "张")
    isNull(列)列 IS NULLisNull("dept_id")
    isNotNull(列)列 IS NOT NULLisNotNull("phone_num")
    orderByAsc(列)ORDER BY 列 ASCorderByAsc("emp_salary")
    orderByDesc(列)ORDER BY 列 DESCorderByDesc("emp_salary")
    groupBy(列)GROUP BY 列groupBy("dept_id")
    having(条件)HAVING 条件(配合 groupBy)having("avg(emp_salary) > 8000")
    select(列...)指定查询哪些列select("emp_id", "emp_name")
    last("limit 5")原样拼接到 SQL 末尾last("limit 5")(谨慎使用)

    五、组合示例

    示例 1:多条件 + 排序

    // 查询:工资 5000~15000、姓名包含「张」、按工资升序
    QueryWrapper<Employee> wrapper = new QueryWrapper<>();
    wrapper.between("emp_salary", 5000, 15000)
           .like("emp_name", "张")
           .orderByAsc("emp_salary");
    
    List<Employee> employeeList = employeeMapper.selectList(wrapper);
    
    ==>  Preparing: SELECT ... FROM t_emp WHERE emp_salary BETWEEN ? AND ? AND emp_name LIKE ? ORDER BY emp_salary ASC
    

    示例 2:指定查询列 + 分组统计

    // 统计每个部门的平均工资,只查平均工资超过 8000 的部门
    QueryWrapper<Employee> wrapper = new QueryWrapper<>();
    wrapper.select("dept_id", "avg(emp_salary) as avgSalary")   // 指定查询列
           .groupBy("dept_id")                                  // 按部门分组
           .having("avg(emp_salary) > 8000");                   // 分组后过滤
    
    List<Map<String, Object>> mapList = employeeMapper.selectMaps(wrapper);
    mapList.forEach(System.out::println);
    
    ==>  Preparing: SELECT dept_id, avg(emp_salary) as avgSalary FROM t_emp GROUP BY dept_id HAVING avg(emp_salary) > 8000
    

    注意:查询了非实体类字段(聚合结果)时,用 selectMaps 接收,结果每行是一个 Map。


    示例 3:统计数量带条件

    // 统计部门 1 的员工人数
    QueryWrapper<Employee> wrapper = new QueryWrapper<>();
    wrapper.eq("dept_id", 1);
    
    Long count = employeeMapper.selectCount(wrapper);
    System.out.println("部门1人数 = " + count);
    

    示例 4:动态拼接条件

    String empNameCondition = "AAA";
    
    // 条件方法(eq()方法)第一个参数:可以传入一个 boolean 类型值
    //      true:条件方法就拼接到 SQL 语句中
    //      false:不拼接
    // 效果:前端传入有效的值,就放入 SQL 中,否则就不参与 SQL 查询条件
    employeeMapper.selectList(new QueryWrapper<Employee>()
            .eq(empNameCondition != null && empNameCondition.length() > 0, "emp_name", empNameCondition))
            .forEach(System.out::println);
    

    六、使用注意事项

    1. Wrapper 中写的是数据库列名(下划线),写错不会在编译期报错,运行时才报「Unknown column」,所以要细心
    2. last() 会把字符串原样拼到 SQL 末尾,有 SQL 注入风险,只在确实需要时使用(如 limit)
    3. 想让条件「可选」(值为 null 时自动跳过),要用带 boolean 参数的重载:eq(条件是否生效, 列, 值),例如 eq(name != null, "emp_name", name)。普通 eq(列, 值) 不管值是不是 null 都会把条件拼进去,要注意
    4. 字符串列名写多了容易错,下一章讲更安全的 LambdaQueryWrapper

    七、本章小结

    • QueryWrapper 把查询条件封装成对象,方法链拼接,自动生成 WHERE
    • 常用:eq / between / like / in / orderByDesc / groupBy / select
    • 条件之间默认 AND 连接,参数占位防注入
    • 查询列与实体不对应时用 selectMaps 接收

    下一章学习 LambdaQueryWrapper:列名从字符串升级为方法引用,写错编译期就报错。

    end