# MyBatis-Plus教程 - 7 条件进阶

前面演示增删改查的时候,查询的是所有数据或者根据ID来查询,下面来详细介绍一下 MyBatis-Plus 中查询功能的使用,如何根据各种条件来查询。

在 MyBatis-Plus 中,查询条件是被封装成条件对象,将条件对象传递给查询方法来实现查询。

常用的查询类是 QueryWrapper 和 LambdaQueryWrapper,关系如下:

上面蓝色的都是抽象类,绿色的 QueryWrapperLambdaQueryWrapper 是用来查询的,红色的 UpdateWrapperLambdaUpdateWrapper 是用来更新数据的,后面章节再讲。

# 7.1 查询对象

下面来讲解一下 QueryWrapper 和 LambdaQueryWrapper 的使用,以及他们的区别。

演示一下根据用户名来查询用户,还是在测试类中进行测试。

# 1 QueryWrapper

首先创建 QueryWrapper 条件对象,然后添加查询条件,将条件对象传递给查询方法。

/**
 * 测试QueryWrapper
 */
@Test
void testQueryWrapper() {
    // 1.创建条件对象
    QueryWrapper<User> queryWrapper = new QueryWrapper();
  
    // 2.设置条件
    queryWrapper.eq("username", "doubi");

    // 3.查询
    User user = userMapper.selectOne(queryWrapper);
    log.info("查询结果:{}", user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • eq() 表示 等于 条件,第一个参数是数据库的字段名称,不是 Java 实体类属性。

  • Mapper 中很多查询方法的参数都是接收条件对象的顶层 Wrapper 抽象类,所以可以传递 QueryWrapperLambdaQueryWrapper

  • 上面 selectOne() 表示只查询一条记录,如果结果是多条记录会报错,要查询多条,可以使用 selectList() 方法。

# 2 LambdaQueryWrapper

LambdaQueryWrapper 和 QueryWrapper 使用方式类似,不同的是添加查询条件的时候,使用的是方法引用,如下:

/**
 * 测试LambdaQueryWrapper
 */
@Test
void testLambdaQueryWrapper() {
    // 1.创建条件对象
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

    // 2.设置条件
    queryWrapper.eq(User::getUsername, "doubi");

    // 3.查询
    User user = userMapper.selectOne(queryWrapper);
    log.info("查询结果:{}", user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

使用方法引用 User::getUsername ,可以避免查询条件的名称写错,所以推荐使用 LambdaQueryWrapper

# 7.2 条件查询

# 1 多个查询条件

在上面根据用户名查询用户,只有一个查询条件,如果有多个查询条件,直接在查询对象上添加即可。

例如根据 用户名年龄 查询用户:

@Test
void testLambdaQueryWrapper() {
    // 1.创建条件对象
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

    // 2.设置条件
    queryWrapper.eq(User::getUsername, "doubi");
    queryWrapper.eq(User::getAge, 30);

    // 3.查询
    User user = userMapper.selectOne(queryWrapper);
    log.info("查询结果:{}", user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

添加的多个查询条件,它们之间的关系是 AND 关系,生成的 SQL 如下:

SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE (username = ? AND age = ?)
1

我们还可以将查询条件放到 Map 中,然后将 Map 添加到查询条件对象上来实现多条件查询。

举个栗子:

/**
 * 测试LambdaQueryWrapper
 */
@Test
void testLambdaQueryWrapper() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("username", "doubi");  // 对应数据库中的字段
    map.put("age", null);

    // 1.创建条件对象
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();

    // 2.添加查询条件
    queryWrapper.allEq(map, false);

    // 3.查询
    User user = userMapper.selectOne(queryWrapper);
    log.info("查询结果:{}", user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

上面使用的是 QueryWrapper ,通过 allEq() 传递的是一个 map,第二个参数表示是否按照空条件查询。上面 map 中 agenull,如果 allEq() 第二个参数为 false,那么执行的 SQL 是:

SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE (username = ?)
1

如果 allEq() 第二个参数为 true,那么 null 也会参与查询,执行的 SQL 是:

SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE (age IS NULL AND username = ?)
1

Map 中的多个值组成的条件也是 AND 关系。

# 2 非空才添加条件

有时候从前端传递的值,在后端查询的时候,当值不为空的时候,才添加条件。

举个栗子:

@Test
void testLambdaQueryWrapper() {
    String username = null;
    // 1.创建条件对象
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

    // 2.设置条件,当username不为空的时候,才会添加username的条件
    queryWrapper.eq(null != username, User::getUsername, "doubi");

    // 3.查询
    User user = userMapper.selectOne(queryWrapper);
    log.info("查询结果:{}", user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

eq() 有一个重载方法,第一个参数是一个 boolean 值,为 true 时,才会将条件添加到 SQL 查询语句中。所以上面当 username 不为空时,才会添加 username 的查询条件。

# 7.3 匹配规则

上面的查询条件使用的是 eq 表示等于,MyBatis-Plus 还提供其他的匹配规则,下面看一些常用的匹配规则。

# 1 等值匹配

queryWrapper.eq(User::getUsername, "doubi");  // 等于
queryWrapper.ne(User::getUsername, "niubi");  // 不等于
1
2

# 2 范围匹配

queryWrapper.gt(User::getAge, 13);  // 大于
queryWrapper.ge(User::getAge, 13);  // 大于等于
queryWrapper.lt(User::getAge, 40);  // 小于
queryWrapper.le(User::getAge, 40);  // 小于等于
queryWrapper.between(User::getAge, 13, 40);  // between在范围之内
queryWrapper.notBetween(User::getAge, 13, 40);  // not between不在范围之内
1
2
3
4
5
6

# 3 模糊匹配

queryWrapper.like(User::getUsername, "doubi");  // 相当于 username like '%doubi%'
queryWrapper.notLike(User::getUsername, "doubi");  // 相当于 username not like '%doubi%'
queryWrapper.likeLeft(User::getUsername, "doubi"); // 相当于 username like '%doubi'
queryWrapper.likeRight(User::getUsername, "doubi"); // 相当于 username like 'doubi%'
1
2
3
4

# 4 判空匹配

queryWrapper.isNull(User::getUsername);  // 相当于 username is null
queryWrapper.isNotNull(User::getUsername);  // 相当于 username is not null
1
2

# 5 in查询

List<String> nameList = new ArrayList<>();
nameList.add("doubi");
nameList.add("niubi");

queryWrapper.in(User::getUsername, nameList);  // 相当于 username in (?,?)
queryWrapper.notIn(User::getUsername, nameList);  // 相当于 username not in (?,?)
1
2
3
4
5
6

# 6 inSql查询

queryWrapper.inSql(User::getUsername, "'doubi','niubi','caibi'");
1

上面也是根据 username 查询,username 在后面的参数中使用 in 查询,生成的 SQL 如下:

SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE (username IN ('doubi','niubi','caibi'))
1

还可以通过一个子查询得出的结果来作为 inSql 的参数,例如:

queryWrapper.inSql(User::getAge, "SELECT age FROM tb_user WHERE age > 20");
1

生成的 SQL 如下:

SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE (age IN (SELECT age FROM tb_user WHERE age > 20))
1

还有 notInSql() 方法,和上面 inSql() 是相反的,这种方式耦合度太高了,我觉得还是少用的好。

# 7 分组查询

例如以 age 进行分组,查询每个年龄对应的人数,并要求人数大于2,那么 sql 如下:

SELECT age, count(*) AS age_count FROM tb_user GROUP BY age HAVING age_count > 2;
1

使用 QueryWrapper 实现如下:

QueryWrapper<User> queryWrapper = new QueryWrapper<>(); //分组字段
queryWrapper.groupBy("age");  // 分组字段
queryWrapper.select("age, count(*) AS age_count");  // 查询的结果
queryWrapper.having("age_count > 2");  // 聚合
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
log.info("查询结果:{}", maps);
1
2
3
4
5
6

最终生成的 SQL 如下:

SELECT age, count(*) AS age_count FROM tb_user GROUP BY age HAVING age_count > 2
1

查询结果是一个 List,其中的元素是 Map 封装了每一行的数据。我觉得这里可读性不强,还不如用原生的 MyBatis 直接写 SQL。

# 7.4 排序

可以指定查询结果按照 单个或多个 字段进行升序和降序的排列。

举个栗子:

按照单个属性 升序/降序 排列:

queryWrapper.orderByAsc(User::getUsername);  // 按照username升序排列
queryWrapper.orderByDesc(User::getUsername);  // 按照username降序排列
1
2

按照多个属性 升序/降序 排列:

queryWrapper.orderByAsc(User::getUsername, User::getAge);  // 按照username和age升序排列
queryWrapper.orderByDesc(User::getUsername, User::getAge);  // 按照username和age降序排列
1
2

还可以指定按照某些字段升序,同时按照某些字段降序排列:

// 按照username升序排列同时age降序排列
queryWrapper.orderByAsc(User::getUsername);  
queryWrapper.orderByDesc(User::getAge);
1
2
3

# 7.5 内嵌逻辑查询

有时候可能需要根据不同的判断,来添加不同的查询条件,我们可以将这些判断和查询条件的处理,放在函数式接口中或使用 Lambda表达式来完成。

举个栗子:

// 1.创建条件对象
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

// 2.添加查询条件
queryWrapper.func(new Consumer<LambdaQueryWrapper<User>>() {
    @Override
    public void accept(LambdaQueryWrapper<User> userLambdaQueryWrapper) {
        boolean condition = true;
        if (condition) {
            userLambdaQueryWrapper.eq(User::getUsername, "doubi");
        }
    }
});

// 3.查询
List<User> userList = userMapper.selectList(queryWrapper);
log.info("查询结果:{}", userList);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

上面的 queryWrapperuserLambdaQueryWrapper 是同一个对象,其实和下面的代码效果是一样的:

// 1.创建条件对象
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

// 2.添加查询条件
boolean condition = true;
if (condition) {
    queryWrapper.eq(User::getUsername, "doubi");
}

// 3.查询
List<User> userList = userMapper.selectList(queryWrapper);
log.info("查询结果:{}", userList);
1
2
3
4
5
6
7
8
9
10
11
12

那你可能懵逼了,怎么感觉脱裤子方笔,搞这么麻烦呢?

有时候判断可能比较复杂,可以将复杂的条件判断封装在 func() 内,保持主逻辑的简洁。另外如果有类似的查询逻辑,可以将 func() 内的逻辑抽取成方法,提高代码复用性。

还可以使用 Lambda 表达式来简化写法:

// 1.创建条件对象
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

// 2.添加查询条件
queryWrapper.func(wrapper -> {
    boolean condition = true;
    if (condition) {
        queryWrapper.eq(User::getUsername, "doubi");
    }
});

// 3.查询
List<User> userList = userMapper.selectList(queryWrapper);
log.info("查询结果:{}", userList);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 7.6 逻辑查询

# 1 AND

上面给 QueryWrapper 添加多个条件,多个条件之间的关系是 AND

下面两种写法效果是一样的:

// 分行编写条件
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, "doubi");
queryWrapper.eq(User::getAge, 30);

// 链式调用
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, "doubi").eq(User::getAge, 30);
1
2
3
4
5
6
7
8

# 2 OR

如果要实现下面的 SQL,该如何实现呢?

select * from tb_user where age < 30 or age > 32;
1

实现如下:

LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.lt(User::getAge, 30).or().gt(User::getAge, 32);

List<User> userList = userMapper.selectList(queryWrapper);
1
2
3
4

使用 or() 连接两个条件。

# 3 逻辑嵌套

实现下面的 SQL:

select * from tb_user where username = 'doubi' and (age < 30 or age > 32);
1

你可能觉得实现如下:

LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, "doubi");
queryWrapper.lt(User::getAge, 30).or().gt(User::getAge, 32);

// 查询
List<User> userList = userMapper.selectList(queryWrapper);
1
2
3
4
5
6

但是上面生成的 SQL 如下:

SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE (username = ? AND age < ? OR age > ?)
1

两个条件语句直接使用 AND 连接了,不是想要的效果。


正确的实现如下:

// 1.创建条件对象
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, "doubi");

// 2.添加查询条件
queryWrapper.and(new Consumer<LambdaQueryWrapper<User>>() {
    @Override
    public void accept(LambdaQueryWrapper<User> wrapper) {
        // age < 30 or age > 32
        wrapper.lt(User::getAge, 30).or().gt(User::getAge, 32);
    }
});

// 3.查询
List<User> userList = userMapper.selectList(queryWrapper);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这里需要使用 and() 方法,参数用到了前面讲到的内嵌逻辑查询,在内嵌逻辑查询中绑定一组查询条件。

可以使用 Lambda 表达式简化:

// 1.创建条件对象
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

// 2.添加查询条件
queryWrapper.eq(User::getUsername, "doubi");
// age < 30 or age > 32
queryWrapper.and(wrapper -> wrapper.lt(User::getAge, 30).or().gt(User::getAge, 32));

// 3.查询
List<User> userList = userMapper.selectList(queryWrapper);
1
2
3
4
5
6
7
8
9
10

# 4 nested实现嵌套

在进行嵌套的时候,除了使用上面的 and(Consumer) (还有一个 or(Consumer) 也可以用来嵌套),还可以使用 nested(Consumer)

nested 适合将一组条件组合在一起,这组条件将以括号形式出现,从而确保逻辑运算的正确性。

举个栗子:

实现如下 SQL:

SELECT * FROM tb_user WHERE (username = ? OR username = ?) AND (age < ? OR age > ?)
1

使用 nested 实现:

// 1.创建条件对象
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

// 2.添加查询条件
queryWrapper.nested(wrapper -> wrapper.eq(User::getUsername, "doubi")
                .or()
                .eq(User::getUsername, "niubi"))
        .nested(wrapper -> wrapper.lt(User::getAge, 30)
                .or()
                .gt(User::getAge, 32));

// 3.查询
List<User> userList = userMapper.selectList(queryWrapper);
1
2
3
4
5
6
7
8
9
10
11
12
13

每个 nested 在生成 SQL 的时候,会在外面添加括号,所以生成的 SQL 如下:

SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE ((username = ? OR username = ?) AND (age < ? OR age > ?))
1

# 7.7 自定义查询

自定义查询就是我们可以写 SQL 语句拼接到 WHERE 后面。

举个栗子:

// 1.创建条件对象
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

// 2.设置条件
queryWrapper.apply("username = {0} and age = {1}", "doubi", 30);

// 3.查询
List<User> userList = userMapper.selectList(queryWrapper);
log.info("查询结果:{}", userList);
1
2
3
4
5
6
7
8
9

apply() 方法第一个参数的 SQL 字符串会拼接到查询语句的 WHERE 后面,后面的参数是查询语句中的参数。

最终生成的 SQL 如下:

SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE (username = ? and age = ?)
1

# 7.8 查询指定字段

MyBatis-Plus 默认在查询的时候会查询 Java 实体类中所有属性对应的数据库字段。我们还可以只查询想要的字段。

举个栗子:

// 1.创建条件对象
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

// 2.设置条件
queryWrapper.select(User::getId, User::getUsername, User::getAge);

// 3.查询
List<User> userList = userMapper.selectList(queryWrapper);
log.info("查询结果:{}", userList);
1
2
3
4
5
6
7
8
9

可以使用 select() 指定要查询的字段,上面只查询 idusernameage 字段,那么对象的其他属性值为空,

生成的 SQL 如下:

SELECT id,username,age FROM tb_user
1