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, "张")
);
核心优势:Lambda 表达式让代码更安全、更易读。我个人建议,新项目直接上 Lambda 版本,别再用字符串了。

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
我的建议:团队开发时,统一约定使用 Lambda 版本的构造器。这样代码风格一致,也避免了字符串拼写错误。如果遇到特别复杂的查询,再考虑用 XML 写原生 SQL。

好了,条件构造器这块就讲这么多。说白了,它就是让你用 Java 代码写 SQL 条件,安全又方便。下一节我们聊聊分页查询,那也是项目里的高频操作。