Java 数据 01:MyBatis-Plus 复杂查询(Lambda+Wrapper 多条件)

MyBatis-Plus 的 复杂查询 是日常开发中最常用的功能之一,尤其当条件动态、多字段组合、嵌套逻辑(and/or)、范围查询、分页排序等场景时,LambdaQueryWrapper 是目前(2025–2026 年)最推荐的方式。

它比老的 QueryWrapper 更安全(编译期就能发现字段名写错)、更现代(Lambda + 方法引用)、代码可读性更高。

下面给你一个系统、由浅入深、覆盖 90% 真实场景的详解。

1. 为什么选 LambdaQueryWrapper 而不是 QueryWrapper?

维度QueryWrapperLambdaQueryWrapper推荐场景(2026共识)
字段安全字符串 "age",改字段名易出错User::getAge,编译期检查Lambda 完胜
可读性一般极高(像写 Java 代码)Lambda
IDE 提示弱(字符串)强(方法引用跳转)Lambda
动态条件支持支持(更优雅)都行,但 Lambda 更简洁
嵌套 or/and需要嵌套 Wrapper支持 lambda 嵌套,更直观Lambda 更友好
社区主流老项目多新项目 95%+ 都在用 LambdaLambda

结论新项目一律用 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&lt;UserVO> searchUsers(UserQueryDTO dto) {
    LambdaQueryWrapper&lt;User> wrapper = Wrappers.&lt;User>lambdaQuery()
        // 姓名模糊(前端传空串也安全)
        .like(StringUtils.isNotBlank(dto.getName()), User::getName, dto.getName())

        // 年龄范围(两个都传才加 between)
        .between(dto.getMinAge() != null &amp;&amp; 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&lt;User> wrapper = Wrappers.&lt;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&lt;User> orNameOrPhone = Wrappers.&lt;User>lambdaQuery()
    .likeRight(User::getName, search)
    .or()
    .likeLeft(User::getPhone, search);

wrapper.and(orNameOrPhone::apply);  // 注意 apply 写法

6. 分页 + 排序 + 常用组合写法

// IService / BaseMapper 通用分页
IPage&lt;User> page = userService.page(
    new Page&lt;>(dto.getPageNum(), dto.getPageSize()),
    wrapper
);

// 或直接用 mapper
IPage&lt;User> pageResult = userMapper.selectPage(new Page&lt;>(1, 10), wrapper);

7. LambdaUpdateWrapper(更新时也推荐)

// 批量逻辑删除(软删)
LambdaUpdateWrapper&lt;User> updateWrapper = Wrappers.&lt;User>lambdaUpdate()
    .set(User::getDeleted, 1)
    .set(User::getDeleteTime, LocalDateTime.now())
    .in(User::getId, idList)
    .eq(User::getDeleted, 0);  // 防止重复删

userMapper.update(null, updateWrapper);

// 涨积分(条件更新)
LambdaUpdateWrapper&lt;User> incScore = Wrappers.&lt;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 代码。

文章已创建 5225

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部