MyBatis-Plus Wrapper 构造器

Wrapper 就是一个用于构造 SQL 语句中 WHERE 条件部分的 Java 工具类。它让你能够通过链式调用的方式,用 Java 代码拼写出各种复杂的查询、更新、删除条件,从而避免手写繁琐的 SQL 片段。Wrapper 家族主要有两个分支:QueryWrapper(用于查询)和 UpdateWrapper(用于更新),以及它们的 Lambda 版本。


1. Wrapper 继承体系(了解即可)

text

1
2
3
4
5
6
Wrapper(顶级接口)
└── AbstractWrapper(抽象类,实现了大部分条件方法)
├── QueryWrapper(用于查询、删除)
├── UpdateWrapper(用于更新,可以 set 字段)
├── LambdaQueryWrapper(Lambda 版 QueryWrapper)
└── LambdaUpdateWrapper(Lambda 版 UpdateWrapper)

在实际开发中,我们通常直接使用 LambdaQueryWrapperLambdaUpdateWrapper,因为它们能用 Lambda 表达式引用实体类的属性(比如 User::getName),避免写错字段名(字符串形式容易写错或重构时遗漏)。


2. 为什么需要 Wrapper?

假如我们要查一张用户表 user,需要满足以下条件:

  • 年龄在 18~30 之间
  • 性别为男
  • 姓名包含“张”
  • 按年龄降序排序

不用 Wrapper 的话,你需要在 Mapper.xml 里手写 SQL:

xml

1
2
3
4
5
6
7
<select id="selectUserList" resultType="User">
SELECT * FROM user
WHERE age BETWEEN 18 AND 30
AND gender = '男'
AND name LIKE '%张%'
ORDER BY age DESC
</select>

如果使用 Wrapper,你不用写 XML,直接在 Java 代码里构造:

java

1
2
3
4
5
6
7
8
9
// 创建条件构造器
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.between(User::getAge, 18, 30)
.eq(User::getGender, "男")
.like(User::getName, "张")
.orderByDesc(User::getAge);

// 直接传入 Mapper 方法执行查询
List<User> users = userMapper.selectList(wrapper);

是不是简洁很多?而且完全避免了 XML 的维护工作。


3. 核心常用方法(以 LambdaQueryWrapper 为例)

🔹 比较条件

方法说明示例
eq等于 =wrapper.eq(User::getAge, 18)age = 18
ne不等于 <>wrapper.ne(User::getAge, 18)age <> 18
gt大于 >wrapper.gt(User::getAge, 18)age > 18
ge大于等于 >=wrapper.ge(User::getAge, 18)
lt小于 <wrapper.lt(User::getAge, 18)
le小于等于 <=wrapper.le(User::getAge, 18)
between在区间内 (包含边界)wrapper.between(User::getAge, 18, 30)age BETWEEN 18 AND 30
notBetween不在区间内
in在集合/数组中wrapper.in(User::getAge, Arrays.asList(18,20,22))
notIn不在集合中
isNull字段为 NULLwrapper.isNull(User::getEmail)
isNotNull字段不为 NULL

🔹 模糊匹配

方法说明示例
like全模糊匹配wrapper.like(User::getName, "张")name LIKE '%张%'
likeLeft左模糊wrapper.likeLeft(User::getName, "张")name LIKE '%张'
likeRight右模糊wrapper.likeRight(User::getName, "张")name LIKE '张%'

🔹 排序

方法说明示例
orderByAsc升序wrapper.orderByAsc(User::getAge, User::getId)ORDER BY age ASC, id ASC
orderByDesc降序wrapper.orderByDesc(User::getAge)
orderBy指定顺序wrapper.orderBy(true, true, User::getAge) 第一个参数是否排序,第二个是否升序

🔹 逻辑组合(AND / OR)

默认情况下,多个条件是 AND 连接。如果要使用 OR,可以调用 or() 方法。

AND 示例(默认)

java

1
2
3
wrapper.eq(User::getGender, "男")
.gt(User::getAge, 18);
// SQL: WHERE gender = '男' AND age > 18

OR 示例

java

1
2
3
4
wrapper.eq(User::getGender, "男")
.or()
.eq(User::getGender, "女");
// SQL: WHERE gender = '男' OR gender = '女'

复杂 OR 嵌套(推荐用 lambda 表达式)

java

1
2
3
wrapper.eq(User::getStatus, 1)
.and(w -> w.eq(User::getAge, 18).or().eq(User::getAge, 20));
// SQL: WHERE status = 1 AND ( age = 18 OR age = 20 )

🔹 指定查询字段

java

1
2
wrapper.select(User::getId, User::getName, User::getAge);
// 只查询这三个字段,SQL 为 SELECT id,name,age FROM ...

🔹 分组和 having

java

1
2
3
wrapper.groupBy(User::getGender)
.having("AVG(age) > {0}", 25);
// SQL: GROUP BY gender HAVING AVG(age) > 25

🔹 其他常用方法

  • last:直接拼接到 SQL 末尾(注意有 SQL 注入风险,谨慎使用
    wrapper.last("limit 1")
  • exists / notExists:子查询
    wrapper.exists("select id from user_log where user_id = user.id")

4. QueryWrapper 与 LambdaQueryWrapper 的区别

  • QueryWrapper:字段名用字符串传入,容易写错,重构实体类时不会自动同步。
    java

    1
    2
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.eq("name", "张三"); // 字符串,容易打错
  • LambdaQueryWrapper:用Lambda 方法引用,编译期就能检查,重构友好。
    java

    1
    2
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
    lqw.eq(User::getName, "张三"); // 推荐使用

强烈建议只用 Lambda 版本


5. UpdateWrapper 的用法

UpdateWrapper 除了能构造 WHERE 条件,还能指定 SET 子句,常用于更新部分字段。

示例1:更新字段值(不通过实体对象)

java

1
2
3
4
5
6
7
// 将所有 age > 30 的用户的状态改为 0,并且年龄增加 1
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(User::getStatus, 0)
.setSql("age = age + 1") // 直接写 SQL 片段
.gt(User::getAge, 30);

userMapper.update(null, updateWrapper); // 第一个参数是实体,null表示只更新 set 指定的字段

示例2:条件更新结合实体

java

1
2
3
4
5
6
7
8
User user = new User();
user.setStatus(1); // 要更新的字段

LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getName, "张三");

userMapper.update(user, wrapper);
// SQL: UPDATE user SET status = 1 WHERE name = '张三'

6. 动态条件:当某个条件为空时不拼接

Wrapper 的方法都提供了带 condition 参数的重载,让你可以控制是否添加该条件。这是开发中最常用的技巧

java

1
2
3
4
5
6
7
8
9
// 假如前端传来参数:name(可为空),age(可为空)
public List<User> searchUser(String name, Integer age) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 只有当 name 不为空时,才拼接 name like 条件
wrapper.like(StringUtils.hasText(name), User::getName, name);
// 只有当 age 不为空时,才拼接 age = 条件
wrapper.eq(age != null, User::getAge, age);
return userMapper.selectList(wrapper);
}

这样当前端不传某个参数时,该条件就不会出现在 SQL 中,非常安全便捷。


7. 实战例子

例1:分页 + 动态条件 + 排序

java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@GetMapping("/list")
public IPage<User> listUsers(@RequestParam(required = false) String name,
@RequestParam(required = false) Integer minAge,
@RequestParam(defaultValue = "1") long current,
@RequestParam(defaultValue = "10") long size) {
// 分页对象
Page<User> page = new Page<>(current, size);
// 构造条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.hasText(name), User::getName, name)
.ge(minAge != null, User::getAge, minAge)
.orderByDesc(User::getCreateTime);
// 执行分页查询
return userMapper.selectPage(page, wrapper);
}

例2:批量删除(根据多个 ID)

java

1
2
3
4
5
List<Long> idList = Arrays.asList(1L, 2L, 3L);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.in(User::getId, idList);
userMapper.delete(wrapper);
// 等价 SQL:DELETE FROM user WHERE id IN (1,2,3)

例3:复杂嵌套查询(年龄>20 并且 (性别男 或者 名字包含“张”))

java

1
2
3
4
5
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 20)
.and(w -> w.eq(User::getGender, "男")
.or()
.like(User::getName, "张"));

8. 注意事项与常见坑

  1. 不要混用 QueryWrapper 和 LambdaQueryWrapper,选定一种风格保持统一。
  2. 条件构造器只适用于单表操作。如果需要多表联查,要么手写 SQL(放在 Mapper.xml 里),要么使用 MP 的 @Select 注解或自定义 Mapper 方法,Wrapper 仍然可以作为参数传入(但需注意字段别名)。
  3. or() 方法会与前面的条件形成括号分组,小心逻辑错误。推荐使用 and(consumer)or(consumer) 显式包裹。
  4. last("limit 1") 等方法会直接拼接到 SQL 末尾,可能与分页插件冲突,且有 SQL 注入风险,尽量用 wrapper.last("limit 1") 仅在测试或特定场景使用。
  5. UpdateWrapper 的 set 方法如果设置字段值为 null,数据库会更新为 null,除非你配置了字段策略(@TableField(strategy = FieldStrategy.IGNORED))。

总结

Wrapper 是 MyBatis-Plus 的灵魂工具,它让我们用 Java 代码优雅地构造 SQL 条件,极大简化了数据访问层的开发。记住三个核心点:

  • 用 LambdaQueryWrapper 和 LambdaUpdateWrapper
  • 善用带 condition 参数的方法实现动态条件
  • 复杂条件用 .and() / .or() 包裹 Lambda 表达式