MyBatis-Plus 的 复杂查询 是日常开发中最常用的功能之一,尤其当条件动态、多字段组合、嵌套逻辑(and/or)、范围查询、分页排序等场景时,LambdaQueryWrapper 是目前(2025–2026 年)最推荐的方式。
它比老的 QueryWrapper 更安全(编译期就能发现字段名写错)、更现代(Lambda + 方法引用)、代码可读性更高。
下面给你一个系统、由浅入深、覆盖 90% 真实场景的详解。
1. 为什么选 LambdaQueryWrapper 而不是 QueryWrapper?
| 维度 | QueryWrapper | LambdaQueryWrapper | 推荐场景(2026共识) |
|---|---|---|---|
| 字段安全 | 字符串 "age",改字段名易出错 | User::getAge,编译期检查 | Lambda 完胜 |
| 可读性 | 一般 | 极高(像写 Java 代码) | Lambda |
| IDE 提示 | 弱(字符串) | 强(方法引用跳转) | Lambda |
| 动态条件 | 支持 | 支持(更优雅) | 都行,但 Lambda 更简洁 |
| 嵌套 or/and | 需要嵌套 Wrapper | 支持 lambda 嵌套,更直观 | Lambda 更友好 |
| 社区主流 | 老项目多 | 新项目 95%+ 都在用 Lambda | Lambda |
结论:新项目一律用 LambdaQueryWrapper,老项目逐步迁移。
2. 快速入门三种创建方式(记一种就行)
// 方式1:最推荐(Wrappers 工具类)
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
// 方式2:new 出来
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 方式3:从 QueryWrapper 转(偶尔用)
LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda();
3. 常见条件方法速查(Lambda 版)
| 需求 | 代码示例(User 实体) | 生成的 SQL 片段(大致) |
|---|---|---|
| 等于 eq | .eq(User::getAge, 18) | age = 18 |
| 不等于 ne | .ne(User::getStatus, 0) | status <> 0 |
| 大于 gt | .gt(User::getScore, 90) | score > 90 |
| 大于等于 ge | .ge(User::getCreateTime, startDate) | create_time >= ? |
| 小于 lt / le | .lt(User::getAge, 30) | age < 30 |
| 模糊 like | .like(User::getName, "张") | name LIKE '%张%' |
| 左模糊 likeLeft | .likeLeft(User::getPhone, "138") | phone LIKE '138%' |
| 右模糊 likeRight | .likeRight(User::getEmail, "@qq.com") | email LIKE '%@qq.com' |
| in | .in(User::getId, idsList) | id IN (1,3,5) |
| notIn | .notIn(User::getRoleId, excludeRoles) | role_id NOT IN (...) |
| between | .between(User::getAge, 18, 35) | age BETWEEN 18 AND 35 |
| isNull / isNotNull | .isNull(User::getDeleteTime) | delete_time IS NULL |
| orderByAsc / Desc | .orderByAsc(User::getCreateTime) | ORDER BY create_time ASC |
| groupBy | .groupBy(User::getDeptId) | GROUP BY dept_id |
| having | .having("count(*) > {0}", 5) | HAVING count(*) > 5 |
4. 动态条件(最常见场景)
public List<UserVO> searchUsers(UserQueryDTO dto) {
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
// 姓名模糊(前端传空串也安全)
.like(StringUtils.isNotBlank(dto.getName()), User::getName, dto.getName())
// 年龄范围(两个都传才加 between)
.between(dto.getMinAge() != null && dto.getMaxAge() != null,
User::getAge, dto.getMinAge(), dto.getMaxAge())
// 状态(枚举或 Integer)
.eq(dto.getStatus() != null, User::getStatus, dto.getStatus())
// 注册时间范围(LocalDateTime)
.ge(dto.getStartTime() != null, User::getCreateTime, dto.getStartTime())
.le(dto.getEndTime() != null, User::getCreateTime, dto.getEndTime())
// 部门多选(in)
.in(CollectionUtils.isNotEmpty(dto.getDeptIds()), User::getDeptId, dto.getDeptIds())
// 排序(默认创建时间降序)
.orderByDesc(User::getCreateTime);
return userMapper.selectList(wrapper);
}
5. 复杂嵌套(and / or 组合)——高频面试 + 生产痛点
// 需求:(姓名 like '张%' OR 手机号 like '138%') AND 年龄 >=18 AND (状态=1 OR 角色 in (2,3))
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
.and(w -> w.likeRight(User::getName, "张")
.or()
.likeLeft(User::getPhone, "138"))
.ge(User::getAge, 18)
.and(w -> w.eq(User::getStatus, 1)
.or()
.in(User::getRoleId, 2, 3));
// 更复杂的嵌套(三层)
.and(w -> w.or(w1 -> w1.eq(User::getType, 1).ge(User::getScore, 90))
.or(w2 -> w2.eq(User::getType, 2).between(User::getAge, 20, 30)))
小技巧:嵌套层级多时,用变量抽取子 wrapper,可读性更好
LambdaQueryWrapper<User> orNameOrPhone = Wrappers.<User>lambdaQuery()
.likeRight(User::getName, search)
.or()
.likeLeft(User::getPhone, search);
wrapper.and(orNameOrPhone::apply); // 注意 apply 写法
6. 分页 + 排序 + 常用组合写法
// IService / BaseMapper 通用分页
IPage<User> page = userService.page(
new Page<>(dto.getPageNum(), dto.getPageSize()),
wrapper
);
// 或直接用 mapper
IPage<User> pageResult = userMapper.selectPage(new Page<>(1, 10), wrapper);
7. LambdaUpdateWrapper(更新时也推荐)
// 批量逻辑删除(软删)
LambdaUpdateWrapper<User> updateWrapper = Wrappers.<User>lambdaUpdate()
.set(User::getDeleted, 1)
.set(User::getDeleteTime, LocalDateTime.now())
.in(User::getId, idList)
.eq(User::getDeleted, 0); // 防止重复删
userMapper.update(null, updateWrapper);
// 涨积分(条件更新)
LambdaUpdateWrapper<User> incScore = Wrappers.<User>lambdaUpdate()
.setSql("score = score + 10")
.eq(User::getId, userId);
userMapper.update(null, incScore);
8. 2025–2026 最佳实践建议
- 永远用
Wrappers.lambdaQuery()/Wrappers.lambdaUpdate()创建 - 动态条件用
条件表达式, 字段, 值三元写法,避免if包裹一堆.eq() - 模糊查询默认前后模糊,敏感字段建议用
likeRight/likeLeft - 时间范围统一用
LocalDateTime,避免时区坑 - 索引友好:排序字段、分组字段、频繁 where 字段要建索引
- 大列表 in 查询超过 1000 条 → 分批或改用 join 子查询
- 生产日志:可通过
wrapper.getSqlSegment()或 MyBatis-Plus 日志打印完整 SQL
你现在遇到的具体场景是哪种?
- 前端多条件搜索(姓名/手机号/时间范围/状态)?
- 嵌套 or + and 的权限过滤?
- 批量更新/逻辑删除?
- 分页 + 排序 + 统计?
告诉我 DTO 结构或需求,我可以直接写出最合适的 wrapper 代码。