4. 条件构造器:QueryWrapper、UpdateWrapper、LambdaQueryWrapper、LambdaUpdateWrapper
说实话,我刚接触 MyBatis Plus 那会儿,最头疼的就是写各种复杂的 SQL 条件。后来发现条件构造器这东西,简直就是救星。它把那些又臭又长的 where 子句,变成了优雅的 Java 代码。今天我就带你把这四个核心构造器彻底搞明白。
4.1 为什么需要条件构造器?
你想想看,传统 MyBatis 写动态 SQL,得用 <if>、<where> 标签在 XML 里拼字符串。项目大了以后,XML 文件动辄上千行,维护起来真的很痛苦。条件构造器解决了两个核心痛点:
- 类型安全:字段名写错了,编译期就能发现,而不是等到运行时才报错
- 动态拼接:条件是否生效,完全由代码逻辑控制,不用再写一堆
<if test="xxx != null">
4.2 QueryWrapper:最基础的查询构造器
QueryWrapper 是最常用的,它通过字符串指定字段名。来看个例子:
// 查询年龄大于18且名字包含"张"的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18)
.like("name", "张")
.orderByDesc("create_time");
List<User> users = userMapper.selectList(wrapper);
这里有个坑,我刚开始用的时候踩过。字段名写的是数据库列名,不是实体类的属性名。比如实体类是 userName,数据库是 user_name,那这里就得写 "user_name"。
常用的方法我整理了一下:
| 方法 | 说明 | 示例 |
|---|---|---|
eq |
等于 | eq("name", "张三") |
ne |
不等于 | ne("status", 0) |
gt |
大于 | gt("age", 18) |
ge |
大于等于 | ge("score", 60) |
lt |
小于 | lt("price", 100) |
le |
小于等于 | le("count", 10) |
like |
模糊匹配 | like("name", "关键词") |
in |
在集合中 | in("id", Arrays.asList(1,2,3)) |
between |
区间 | between("age", 18, 30) |
QueryWrapper 的字段名是硬编码的字符串。如果数据库字段改名,你得手动改所有地方。这也是为什么后来 Lambda 版本更受欢迎的原因。
4.3 UpdateWrapper:灵活的更新构造器
更新操作时,UpdateWrapper 能让你只更新特定字段,而不是整个实体。我有个项目需要批量修改用户状态,用这个就特别方便:
// 将部门为"技术部"的用户状态改为2
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("dept", "技术部")
.set("status", 2)
.set("update_time", new Date());
userMapper.update(null, wrapper);
注意第一个参数传了 null,因为更新条件全在 wrapper 里了。如果你传了实体对象,那实体里非空的字段也会参与更新,容易出问题。
我曾经犯过一个错:更新时传了实体,实体里有个字段不小心被赋了默认值,结果把数据库里正确的数据给覆盖了。从那以后,只要用 UpdateWrapper,我第一个参数一律传 null。
4.4 LambdaQueryWrapper:类型安全的查询
这个是我现在用得最多的。它用 Lambda 表达式引用实体类的 getter 方法,字段名写错了编译直接报错:
// 使用 LambdaQueryWrapper
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18)
.like(User::getName, "张")
.orderByDesc(User::getCreateTime);
List<User> users = userMapper.selectList(wrapper);
你看,User::getAge 比写 "age" 安全多了。重构字段名时,IDE 的全局重命名功能也能正常工作。
还有个写法更简洁,直接用 Wrappers.lambdaQuery():
List<User> users = userMapper.selectList(
Wrappers.<User>lambdaQuery()
.gt(User::getAge, 18)
.like(User::getName, "张")
);
4.5 LambdaUpdateWrapper:安全的更新构造器
和 LambdaQueryWrapper 类似,LambdaUpdateWrapper 也是用 Lambda 引用字段:
// 批量更新用户状态
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getDept, "技术部")
.set(User::getStatus, 2)
.set(User::getUpdateTime, new Date());
userMapper.update(null, wrapper);
或者用 Wrappers.lambdaUpdate():
userMapper.update(null,
Wrappers.<User>lambdaUpdate()
.eq(User::getDept, "技术部")
.set(User::getStatus, 2)
);
4.6 条件构造器的进阶用法
实际项目中,条件往往是动态的。比如用户搜索时,可能只填了名字,没填年龄。这时候就需要条件判断:
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 只有 name 不为空时才拼接条件
if (StringUtils.isNotBlank(name)) {
wrapper.like(User::getName, name);
}
// 只有 age 不为 null 时才拼接条件
if (age != null) {
wrapper.eq(User::getAge, age);
}
// 时间范围查询
if (startTime != null && endTime != null) {
wrapper.between(User::getCreateTime, startTime, endTime);
}
List<User> users = userMapper.selectList(wrapper);
还有个更优雅的写法,用 condition 参数:
// 第一个参数是 boolean,true 才拼接条件
wrapper.like(StringUtils.isNotBlank(name), User::getName, name)
.eq(age != null, User::getAge, age);
嗯,这个写法我特别喜欢。一行代码搞定条件判断,不用写 if-else 了。
4.7 避坑指南
最后分享几个我踩过的坑:
- 字段名写错:用
QueryWrapper时,字段名一定要和数据库一致。我建议直接复制数据库的字段名,别手打。 - 条件覆盖:多次调用同一个方法,比如两次
eq,生成的是 AND 关系。如果想用 OR,得用or()方法。 - 空指针:
in方法传的集合如果是空的,生成的 SQL 会报错。记得先判空。 - 性能问题:
like方法默认是两边加%,会导致索引失效。如果只需要右模糊,用likeRight。
好了,条件构造器这块就讲这么多。说白了,它就是让你用 Java 代码写 SQL 条件,安全又方便。下一节我们聊聊分页查询,那也是项目里的高频操作。