[toc]
| 单词 | 词性 | 音标 | 含义 |
|---|---|---|---|
| Query | v. | /ˈkwɪəri/ | 查询(QueryWrapper 是封装查询条件的包装器对象) |
| Wrapper | n. | /ˈræpə(r)/ | 包装器(把多个查询条件包装成一个对象传给 Mapper 方法) |
| Between | prep. | /bɪˈtwiːn/ | 在…之间(between 方法生成 BETWEEN ? AND ? 区间条件) |
| Like | prep. | /laɪk/ | 像(like 方法生成模糊查询 LIKE '%值%') |
| Order | n. | /ˈɔː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
要点:
- 每调用一个方法,就往 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 值2 | between("emp_salary", 5000, 10000) |
notBetween(列, 值1, 值2) | 列 NOT BETWEEN 值1 AND 值2 | notBetween(...) |
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 NULL | isNull("dept_id") |
isNotNull(列) | 列 IS NOT NULL | isNotNull("phone_num") |
orderByAsc(列) | ORDER BY 列 ASC | orderByAsc("emp_salary") |
orderByDesc(列) | ORDER BY 列 DESC | orderByDesc("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);
六、使用注意事项
- Wrapper 中写的是数据库列名(下划线),写错不会在编译期报错,运行时才报「Unknown column」,所以要细心
last()会把字符串原样拼到 SQL 末尾,有 SQL 注入风险,只在确实需要时使用(如 limit)- 想让条件「可选」(值为 null 时自动跳过),要用带 boolean 参数的重载:
eq(条件是否生效, 列, 值),例如eq(name != null, "emp_name", name)。普通eq(列, 值)不管值是不是 null 都会把条件拼进去,要注意 - 字符串列名写多了容易错,下一章讲更安全的 LambdaQueryWrapper
七、本章小结
- QueryWrapper 把查询条件封装成对象,方法链拼接,自动生成 WHERE
- 常用:
eq/between/like/in/orderByDesc/groupBy/select - 条件之间默认 AND 连接,参数占位防注入
- 查询列与实体不对应时用
selectMaps接收
下一章学习 LambdaQueryWrapper:列名从字符串升级为方法引用,写错编译期就报错。