MyBatis-Plus 条件构造器详解
目录
- [1 Wrapper 体系概览](#1 Wrapper 体系概览)
- [2 QueryWrapper](#2 QueryWrapper)
- [2.1 组装查询条件](#2.1 组装查询条件)
- [2.2 组装排序条件](#2.2 组装排序条件)
- [2.3 组装删除条件](#2.3 组装删除条件)
- [2.4 条件的优先级](#2.4 条件的优先级)
- [2.5 组装 select 子句](#2.5 组装 select 子句)
- [2.6 实现子查询](#2.6 实现子查询)
- [3 UpdateWrapper](#3 UpdateWrapper)
- [4 Condition ------ 动态组装条件](#4 Condition —— 动态组装条件)
- [4.1 传统方式:if 判断](#4.1 传统方式:if 判断)
- [4.2 简化方式:condition 参数](#4.2 简化方式:condition 参数)
- [5 LambdaQueryWrapper](#5 LambdaQueryWrapper)
- [6 LambdaUpdateWrapper](#6 LambdaUpdateWrapper)
1 Wrapper 体系概览
MyBatis-Plus 提供了一套条件构造器(Wrapper),用于构建 SQL 中的 WHERE 条件,避免手写 SQL 字符串。其类层次结构如下:
| 类名 | 说明 |
|---|---|
Wrapper |
条件构造抽象类,最顶端父类 |
AbstractWrapper |
查询条件封装,生成 SQL 的 WHERE 条件 |
QueryWrapper |
查询/删除条件封装 |
UpdateWrapper |
更新条件封装,可同时设置 SET 子句和 WHERE 条件 |
AbstractLambdaWrapper |
Lambda 语法的抽象父类 |
LambdaQueryWrapper |
Lambda 语法的查询条件封装 |
LambdaUpdateWrapper |
Lambda 语法的更新条件封装 |
常用的条件方法速查:
| 方法 | 对应 SQL | 示例 |
|---|---|---|
eq |
= ? |
.eq("age", 20) |
ne |
<> ? |
.ne("age", 20) |
gt |
> ? |
.gt("age", 20) |
ge |
>= ? |
.ge("age", 20) |
lt |
< ? |
.lt("age", 20) |
le |
<= ? |
.le("age", 20) |
like |
LIKE '%?%' |
.like("name", "a") |
between |
BETWEEN ? AND ? |
.between("age", 20, 30) |
isNull |
IS NULL |
.isNull("email") |
isNotNull |
IS NOT NULL |
.isNotNull("email") |
in |
IN (?, ?) |
.in("id", 1, 2, 3) |
inSql |
IN (子查询) |
.inSql("id", "select id from ...") |
orderByAsc |
ORDER BY ? ASC |
.orderByAsc("id") |
orderByDesc |
ORDER BY ? DESC |
.orderByDesc("age") |
or |
OR |
.or() |
and |
AND (嵌套) |
.and(i -> i.gt(...).or()...) |
select |
指定查询字段 | .select("name", "age") |
set |
SET 字段=值 |
.set("age", 18) |
2 QueryWrapper
QueryWrapper 是最常用的条件构造器,用于构建 SELECT、DELETE 语句的 WHERE 条件。
2.1 组装查询条件
查询用户名包含 a、年龄在 20 到 30 之间、且邮箱不为 null 的用户:
java
@Test
public void test01() {
// SELECT id,username AS name,age,email,is_deleted FROM t_user
// WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username", "a")
.between("age", 20, 30)
.isNotNull("email");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
多个条件通过链式调用拼接,默认使用 AND 连接。
2.2 组装排序条件
按年龄降序排列,年龄相同时按 id 升序排列:
java
@Test
public void test02() {
// SELECT id,username AS name,age,email,is_deleted FROM t_user
// WHERE is_deleted=0 ORDER BY age DESC, id ASC
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("id");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
2.3 组装删除条件
条件构造器不仅能用于查询,也可以用于构建删除语句的条件:
java
@Test
public void test03() {
// DELETE FROM t_user WHERE (email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("受影响的行数:" + result);
}
2.4 条件的优先级
使用 or() 方法可以将默认的 AND 连接切换为 OR:
java
@Test
public void test04() {
// 将(用户名包含a并且年龄大于20)或邮箱为null的用户信息修改
// UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND age > ? OR email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username", "a")
.gt("age", 20)
.or()
.isNull("email");
User user = new User();
user.setAge(38);
user.setEmail("kobe@qq.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("受影响的行数:" + result);
}
如果需要改变优先级(加括号),可以使用 and() 方法传入 Lambda 表达式,Lambda 内部的条件会被括号包裹:
java
@Test
public void test04Priority() {
// 将用户名包含a并且(年龄大于20或邮箱为null)的用户信息修改
// UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username", "a")
.and(i -> i.gt("age", 20)
.or()
.isNull("email"));
User user = new User();
user.setAge(18);
user.setEmail("oscar@163.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("受影响的行数:" + result);
}
关键区别:
.or():改变紧随其后一个条件的连接方式为 OR.and(i -> ...):Lambda 内部的条件用括号包裹,实现优先级提升
2.5 组装 select 子句
默认查询会返回所有字段。使用 select() 方法可以指定只查询部分字段:
java
@Test
public void test05() {
// SELECT username, age FROM t_user
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("username", "age");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
这里使用 selectMaps() 而非 selectList(),返回 Map<String, Object> 集合,避免未查询的字段在实体对象中显示为 null。
2.6 实现子查询
使用 inSql() 方法可以构造 IN 子查询:
java
@Test
public void test06() {
// SELECT id,username AS name,age,email,is_deleted FROM t_user
// WHERE (id IN (select id from t_user where id <= 3))
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("id", "select id from t_user where id <= 3");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
inSql() 的第二个参数是原生 SQL 字符串,适合需要嵌套子查询的场景。
3 UpdateWrapper
UpdateWrapper 可以同时设置 WHERE 条件和 SET 子句,不需要依赖实体对象来传递更新字段:
java
@Test
public void test07() {
// UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper
.set("age", 18)
.set("email", "user@qq.com")
.like("username", "a")
.and(i -> i.gt("age", 20).or().isNull("email"));
int result = userMapper.update(null, updateWrapper);
System.out.println(result);
}
QueryWrapper vs UpdateWrapper 在更新场景下的区别:
对比项 QueryWrapper UpdateWrapper 更新字段来源 通过实体对象的非 null 属性 通过 .set()方法直接指定update()第一个参数必须传入实体对象 可以传 null 适用场景 更新字段由实体对象决定 需要精确控制更新字段,或需要将字段设为 null
4 Condition ------ 动态组装条件
实际开发中,查询条件往往来自用户输入,是可选的。如果用户没有输入某个条件,就不应该将其组装到 SQL 中。
4.1 传统方式:if 判断
java
@Test
public void test08() {
String username = null;
Integer ageBegin = 10;
Integer ageEnd = 24;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(username)) {
queryWrapper.like("username", "a");
}
if (ageBegin != null) {
queryWrapper.ge("age", ageBegin);
}
if (ageEnd != null) {
queryWrapper.le("age", ageEnd);
}
// username 为 null,不参与条件组装
// SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >= ? AND age <= ?)
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
这种方式功能正确,但 if 判断较多,代码比较冗长。
4.2 简化方式:condition 参数
MyBatis-Plus 的条件方法都提供了带 condition 参数的重载版本。当 condition 为 true 时才组装该条件,为 false 时自动跳过:
java
@Test
public void test08UseCondition() {
String username = null;
Integer ageBegin = 10;
Integer ageEnd = 24;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username), "username", "a")
.ge(ageBegin != null, "age", ageBegin)
.le(ageEnd != null, "age", ageEnd);
// SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >= ? AND age <= ?)
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
效果与 if 判断完全一致,但代码更简洁。方法签名中第一个参数即为 condition,例如 like(boolean condition, String column, Object val)。
5 LambdaQueryWrapper
QueryWrapper 中的字段名使用字符串硬编码,如果字段名拼写错误,编译期不会报错,运行时才会发现。LambdaQueryWrapper 通过方法引用代替字符串,将字段名的校验提前到编译期:
java
@Test
public void test09() {
String username = "a";
Integer ageBegin = 10;
Integer ageEnd = 24;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper
.like(StringUtils.isNotBlank(username), User::getName, username)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null, User::getAge, ageEnd);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
QueryWrapper vs LambdaQueryWrapper:
对比项 QueryWrapper LambdaQueryWrapper 字段引用方式 字符串: "username"方法引用: User::getName编译期检查 ❌ 字段名拼错不会报编译错误 ✅ 方法引用不存在时编译报错 重构安全性 ❌ 重命名字段后需手动修改字符串 ✅ IDE 自动同步 推荐程度 简单场景可用 推荐使用
6 LambdaUpdateWrapper
与 LambdaQueryWrapper 类似,LambdaUpdateWrapper 用方法引用代替字符串来构建更新语句:
java
@Test
public void test10() {
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(User::getAge, 18)
.set(User::getEmail, "user@atguigu.com")
.like(User::getName, "a")
.and(i -> i.lt(User::getAge, 24)
.or()
.isNull(User::getEmail));
User user = new User();
int result = userMapper.update(user, updateWrapper);
System.out.println("受影响的行数:" + result);
}
实际开发中,推荐优先使用 Lambda 系列的条件构造器 (
LambdaQueryWrapper和LambdaUpdateWrapper),编译期即可发现字段引用错误,减少线上问题。