# MyBatis-Plus教程 - 7 条件进阶
前面演示增删改查的时候,查询的是所有数据或者根据ID来查询,下面来详细介绍一下 MyBatis-Plus 中查询功能的使用,如何根据各种条件来查询。
在 MyBatis-Plus 中,查询条件是被封装成条件对象,将条件对象传递给查询方法来实现查询。
常用的查询类是 QueryWrapper 和 LambdaQueryWrapper,关系如下:
上面蓝色的都是抽象类,绿色的 QueryWrapper
和 LambdaQueryWrapper
是用来查询的,红色的 UpdateWrapper
和 LambdaUpdateWrapper
是用来更新数据的,后面章节再讲。
# 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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
eq()
表示等于
条件,第一个参数是数据库的字段名称,不是 Java 实体类属性。Mapper 中很多查询方法的参数都是接收条件对象的顶层
Wrapper
抽象类,所以可以传递QueryWrapper
和LambdaQueryWrapper
。上面
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);
}
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);
}
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 = ?)
我们还可以将查询条件放到 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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
上面使用的是 QueryWrapper
,通过 allEq()
传递的是一个 map,第二个参数表示是否按照空条件查询。上面 map 中 age
为 null
,如果 allEq()
第二个参数为 false
,那么执行的 SQL 是:
SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE (username = ?)
如果 allEq()
第二个参数为 true
,那么 null
也会参与查询,执行的 SQL 是:
SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE (age IS NULL AND username = ?)
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);
}
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"); // 不等于
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不在范围之内
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%'
2
3
4
# 4 判空匹配
queryWrapper.isNull(User::getUsername); // 相当于 username is null
queryWrapper.isNotNull(User::getUsername); // 相当于 username is not null
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 (?,?)
2
3
4
5
6
# 6 inSql查询
queryWrapper.inSql(User::getUsername, "'doubi','niubi','caibi'");
上面也是根据 username
查询,username
在后面的参数中使用 in
查询,生成的 SQL 如下:
SELECT id,username,password,age,email,create_time,update_time FROM tb_user WHERE (username IN ('doubi','niubi','caibi'))
还可以通过一个子查询得出的结果来作为 inSql
的参数,例如:
queryWrapper.inSql(User::getAge, "SELECT age FROM tb_user WHERE age > 20");
生成的 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))
还有 notInSql()
方法,和上面 inSql()
是相反的,这种方式耦合度太高了,我觉得还是少用的好。
# 7 分组查询
例如以 age 进行分组,查询每个年龄对应的人数,并要求人数大于2,那么 sql 如下:
SELECT age, count(*) AS age_count FROM tb_user GROUP BY age HAVING age_count > 2;
使用 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);
2
3
4
5
6
最终生成的 SQL 如下:
SELECT age, count(*) AS age_count FROM tb_user GROUP BY age HAVING age_count > 2
查询结果是一个 List,其中的元素是 Map 封装了每一行的数据。我觉得这里可读性不强,还不如用原生的 MyBatis 直接写 SQL。
# 7.4 排序
可以指定查询结果按照 单个或多个 字段进行升序和降序的排列。
举个栗子:
按照单个属性 升序/降序
排列:
queryWrapper.orderByAsc(User::getUsername); // 按照username升序排列
queryWrapper.orderByDesc(User::getUsername); // 按照username降序排列
2
按照多个属性 升序/降序
排列:
queryWrapper.orderByAsc(User::getUsername, User::getAge); // 按照username和age升序排列
queryWrapper.orderByDesc(User::getUsername, User::getAge); // 按照username和age降序排列
2
还可以指定按照某些字段升序,同时按照某些字段降序排列:
// 按照username升序排列同时age降序排列
queryWrapper.orderByAsc(User::getUsername);
queryWrapper.orderByDesc(User::getAge);
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);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上面的 queryWrapper
和 userLambdaQueryWrapper
是同一个对象,其实和下面的代码效果是一样的:
// 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);
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);
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);
2
3
4
5
6
7
8
# 2 OR
如果要实现下面的 SQL,该如何实现呢?
select * from tb_user where age < 30 or age > 32;
实现如下:
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.lt(User::getAge, 30).or().gt(User::getAge, 32);
List<User> userList = userMapper.selectList(queryWrapper);
2
3
4
使用 or()
连接两个条件。
# 3 逻辑嵌套
实现下面的 SQL:
select * from tb_user where username = 'doubi' and (age < 30 or age > 32);
你可能觉得实现如下:
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);
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 > ?)
两个条件语句直接使用 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);
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);
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 > ?)
使用 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);
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 > ?))
# 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);
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 = ?)
# 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);
2
3
4
5
6
7
8
9
可以使用 select()
指定要查询的字段,上面只查询 id
、username
、age
字段,那么对象的其他属性值为空,
生成的 SQL 如下:
SELECT id,username,age FROM tb_user