MyBatis-Plus 条件构造器详解

MyBatis-Plus 条件构造器详解


目录

  • [1 Wrapper 体系概览](#1 Wrapper 体系概览)
  • [2 QueryWrapper](#2 QueryWrapper)
    • [2.1 组装查询条件](#2.1 组装查询条件)
    • [2.2 组装排序条件](#2.2 组装排序条件)
    • [2.3 组装删除条件](#2.3 组装删除条件)
    • [2.4 条件的优先级](#2.4 条件的优先级)
    • [2.5 组装 select 子句](#2.5 组装 select 子句)
    • [2.6 实现子查询](#2.6 实现子查询)
  • [3 UpdateWrapper](#3 UpdateWrapper)
  • [4 Condition ------ 动态组装条件](#4 Condition —— 动态组装条件)
    • [4.1 传统方式:if 判断](#4.1 传统方式:if 判断)
    • [4.2 简化方式:condition 参数](#4.2 简化方式:condition 参数)
  • [5 LambdaQueryWrapper](#5 LambdaQueryWrapper)
  • [6 LambdaUpdateWrapper](#6 LambdaUpdateWrapper)

1 Wrapper 体系概览

MyBatis-Plus 提供了一套条件构造器(Wrapper),用于构建 SQL 中的 WHERE 条件,避免手写 SQL 字符串。其类层次结构如下:

类名 说明
Wrapper 条件构造抽象类,最顶端父类
AbstractWrapper 查询条件封装,生成 SQL 的 WHERE 条件
QueryWrapper 查询/删除条件封装
UpdateWrapper 更新条件封装,可同时设置 SET 子句和 WHERE 条件
AbstractLambdaWrapper Lambda 语法的抽象父类
LambdaQueryWrapper Lambda 语法的查询条件封装
LambdaUpdateWrapper Lambda 语法的更新条件封装

常用的条件方法速查:

方法 对应 SQL 示例
eq = ? .eq("age", 20)
ne <> ? .ne("age", 20)
gt > ? .gt("age", 20)
ge >= ? .ge("age", 20)
lt < ? .lt("age", 20)
le <= ? .le("age", 20)
like LIKE '%?%' .like("name", "a")
between BETWEEN ? AND ? .between("age", 20, 30)
isNull IS NULL .isNull("email")
isNotNull IS NOT NULL .isNotNull("email")
in IN (?, ?) .in("id", 1, 2, 3)
inSql IN (子查询) .inSql("id", "select id from ...")
orderByAsc ORDER BY ? ASC .orderByAsc("id")
orderByDesc ORDER BY ? DESC .orderByDesc("age")
or OR .or()
and AND (嵌套) .and(i -> i.gt(...).or()...)
select 指定查询字段 .select("name", "age")
set SET 字段=值 .set("age", 18)

2 QueryWrapper

QueryWrapper 是最常用的条件构造器,用于构建 SELECT、DELETE 语句的 WHERE 条件。

2.1 组装查询条件

查询用户名包含 a、年龄在 20 到 30 之间、且邮箱不为 null 的用户:

java 复制代码
@Test
public void test01() {
    // SELECT id,username AS name,age,email,is_deleted FROM t_user
    // WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("username", "a")
                .between("age", 20, 30)
                .isNotNull("email");
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

多个条件通过链式调用拼接,默认使用 AND 连接。

2.2 组装排序条件

按年龄降序排列,年龄相同时按 id 升序排列:

java 复制代码
@Test
public void test02() {
    // SELECT id,username AS name,age,email,is_deleted FROM t_user
    // WHERE is_deleted=0 ORDER BY age DESC, id ASC
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.orderByDesc("age")
                .orderByAsc("id");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

2.3 组装删除条件

条件构造器不仅能用于查询,也可以用于构建删除语句的条件:

java 复制代码
@Test
public void test03() {
    // DELETE FROM t_user WHERE (email IS NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.isNull("email");
    int result = userMapper.delete(queryWrapper);
    System.out.println("受影响的行数:" + result);
}

2.4 条件的优先级

使用 or() 方法可以将默认的 AND 连接切换为 OR:

java 复制代码
@Test
public void test04() {
    // 将(用户名包含a并且年龄大于20)或邮箱为null的用户信息修改
    // UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND age > ? OR email IS NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("username", "a")
                .gt("age", 20)
                .or()
                .isNull("email");
    User user = new User();
    user.setAge(38);
    user.setEmail("kobe@qq.com");
    int result = userMapper.update(user, queryWrapper);
    System.out.println("受影响的行数:" + result);
}

如果需要改变优先级(加括号),可以使用 and() 方法传入 Lambda 表达式,Lambda 内部的条件会被括号包裹:

java 复制代码
@Test
public void test04Priority() {
    // 将用户名包含a并且(年龄大于20或邮箱为null)的用户信息修改
    // UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("username", "a")
                .and(i -> i.gt("age", 20)
                           .or()
                           .isNull("email"));
    User user = new User();
    user.setAge(18);
    user.setEmail("oscar@163.com");
    int result = userMapper.update(user, queryWrapper);
    System.out.println("受影响的行数:" + result);
}

关键区别:

  • .or() :改变紧随其后一个条件的连接方式为 OR
  • .and(i -> ...) :Lambda 内部的条件用括号包裹,实现优先级提升

2.5 组装 select 子句

默认查询会返回所有字段。使用 select() 方法可以指定只查询部分字段:

java 复制代码
@Test
public void test05() {
    // SELECT username, age FROM t_user
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("username", "age");
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(System.out::println);
}

这里使用 selectMaps() 而非 selectList(),返回 Map<String, Object> 集合,避免未查询的字段在实体对象中显示为 null。

2.6 实现子查询

使用 inSql() 方法可以构造 IN 子查询:

java 复制代码
@Test
public void test06() {
    // SELECT id,username AS name,age,email,is_deleted FROM t_user
    // WHERE (id IN (select id from t_user where id <= 3))
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.inSql("id", "select id from t_user where id <= 3");
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

inSql() 的第二个参数是原生 SQL 字符串,适合需要嵌套子查询的场景。


3 UpdateWrapper

UpdateWrapper 可以同时设置 WHERE 条件和 SET 子句,不需要依赖实体对象来传递更新字段:

java 复制代码
@Test
public void test07() {
    // UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper
        .set("age", 18)
        .set("email", "user@qq.com")
        .like("username", "a")
        .and(i -> i.gt("age", 20).or().isNull("email"));
    int result = userMapper.update(null, updateWrapper);
    System.out.println(result);
}

QueryWrapper vs UpdateWrapper 在更新场景下的区别:

对比项 QueryWrapper UpdateWrapper
更新字段来源 通过实体对象的非 null 属性 通过 .set() 方法直接指定
update() 第一个参数 必须传入实体对象 可以传 null
适用场景 更新字段由实体对象决定 需要精确控制更新字段,或需要将字段设为 null

4 Condition ------ 动态组装条件

实际开发中,查询条件往往来自用户输入,是可选的。如果用户没有输入某个条件,就不应该将其组装到 SQL 中。

4.1 传统方式:if 判断

java 复制代码
@Test
public void test08() {
    String username = null;
    Integer ageBegin = 10;
    Integer ageEnd = 24;

    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    if (StringUtils.isNotBlank(username)) {
        queryWrapper.like("username", "a");
    }
    if (ageBegin != null) {
        queryWrapper.ge("age", ageBegin);
    }
    if (ageEnd != null) {
        queryWrapper.le("age", ageEnd);
    }
    // username 为 null,不参与条件组装
    // SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >= ? AND age <= ?)
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

这种方式功能正确,但 if 判断较多,代码比较冗长。

4.2 简化方式:condition 参数

MyBatis-Plus 的条件方法都提供了带 condition 参数的重载版本。当 conditiontrue 时才组装该条件,为 false 时自动跳过:

java 复制代码
@Test
public void test08UseCondition() {
    String username = null;
    Integer ageBegin = 10;
    Integer ageEnd = 24;

    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like(StringUtils.isNotBlank(username), "username", "a")
                .ge(ageBegin != null, "age", ageBegin)
                .le(ageEnd != null, "age", ageEnd);
    // SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >= ? AND age <= ?)
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

效果与 if 判断完全一致,但代码更简洁。方法签名中第一个参数即为 condition,例如 like(boolean condition, String column, Object val)


5 LambdaQueryWrapper

QueryWrapper 中的字段名使用字符串硬编码,如果字段名拼写错误,编译期不会报错,运行时才会发现。LambdaQueryWrapper 通过方法引用代替字符串,将字段名的校验提前到编译期:

java 复制代码
@Test
public void test09() {
    String username = "a";
    Integer ageBegin = 10;
    Integer ageEnd = 24;

    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper
        .like(StringUtils.isNotBlank(username), User::getName, username)
        .ge(ageBegin != null, User::getAge, ageBegin)
        .le(ageEnd != null, User::getAge, ageEnd);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

QueryWrapper vs LambdaQueryWrapper:

对比项 QueryWrapper LambdaQueryWrapper
字段引用方式 字符串:"username" 方法引用:User::getName
编译期检查 ❌ 字段名拼错不会报编译错误 ✅ 方法引用不存在时编译报错
重构安全性 ❌ 重命名字段后需手动修改字符串 ✅ IDE 自动同步
推荐程度 简单场景可用 推荐使用

6 LambdaUpdateWrapper

LambdaQueryWrapper 类似,LambdaUpdateWrapper 用方法引用代替字符串来构建更新语句:

java 复制代码
@Test
public void test10() {
    LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
    updateWrapper.set(User::getAge, 18)
                 .set(User::getEmail, "user@atguigu.com")
                 .like(User::getName, "a")
                 .and(i -> i.lt(User::getAge, 24)
                            .or()
                            .isNull(User::getEmail));
    User user = new User();
    int result = userMapper.update(user, updateWrapper);
    System.out.println("受影响的行数:" + result);
}

实际开发中,推荐优先使用 Lambda 系列的条件构造器LambdaQueryWrapperLambdaUpdateWrapper),编译期即可发现字段引用错误,减少线上问题。