条件构造器
条件构造器是 MyBatis Plus 最强大的功能之一,它提供了优雅的方式来构建复杂的查询条件。本章将详细介绍各种条件构造器的使用方法。
Wrapper 家族
Wrapper 是 MyBatis Plus 条件构造器的核心,它提供了一种类型安全、优雅的方式来构建复杂的 SQL 查询条件。通过链式调用,可以直观地表达各种查询逻辑。
什么是 Wrapper?
Wrapper 本质上是一个 SQL 条件片段的构建器。当你调用 selectList(wrapper) 时,MyBatis Plus 会将 Wrapper 中构建的条件转换为 SQL 的 WHERE 子句。这种设计模式称为"构建器模式",它将复杂对象的构建过程封装起来,使调用者可以一步步构建最终的产品。
Wrapper 类型一览
MyBatis Plus 提供了多种 Wrapper 类型,适用于不同场景:
| Wrapper 类 | 说明 | 使用场景 |
|---|---|---|
QueryWrapper | 查询条件封装 | 简单查询,字段名字符串 |
LambdaQueryWrapper | Lambda 表达式查询条件 | 推荐,字段类型安全 |
UpdateWrapper | 更新条件封装 | 条件更新,字段名字符串 |
LambdaUpdateWrapper | Lambda 表达式更新条件 | 推荐,字段类型安全 |
AbstractWrapper | 所有 Wrapper 的父类 | 提供通用方法,不直接使用 |
为什么推荐使用 Lambda?
在实际开发中,强烈建议使用 Lambda 版本的 Wrapper(LambdaQueryWrapper、LambdaUpdateWrapper),原因如下:
1. 避免字段名硬编码
// 不推荐:字段名字符串,容易拼写错误
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "张三"); // 如果字段名写错,运行时才会发现
// 推荐:Lambda 表达式,编译期检查
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.eq(User::getName, "张三"); // 编译期就能发现错误
2. 支持重构
当实体类字段名变更时,IDE 可以自动更新所有 Lambda 引用,而字符串字段名需要手动查找替换。
3. 代码可读性更好
// 字符串方式:字段名含义不明显
wrapper.eq("n", "张三"); // n 是什么字段?
// Lambda 方式:字段名清晰明了
lambdaWrapper.eq(User::getName, "张三");
4. 完整的 IDE 支持
IDE 可以提供自动补全、跳转定义、查找引用等功能。
比较操作
比较操作是条件构造器最基础的功能,用于构建等于、不等于、大于、小于等条件。
等于 eq
eq 方法用于构建等于条件,对应 SQL 的 = 操作符:
@Test
void testEq() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "张三");
List<User> users = userMapper.selectList(wrapper);
// 生成 SQL: SELECT * FROM t_user WHERE name = '张三'
}
条件参数用法:所有 Wrapper 方法都支持第一个参数作为条件判断,只有当条件为 true 时才会添加该条件:
@Test
void testEqWithCondition() {
String name = "张三"; // 假设这个值可能为 null
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 只有 name != null 时才添加这个条件
wrapper.eq(name != null, User::getName, name);
List<User> users = userMapper.selectList(wrapper);
// 如果 name 为 null,生成 SQL: SELECT * FROM t_user
// 如果 name 不为 null,生成 SQL: SELECT * FROM t_user WHERE name = '张三'
}
这种写法在动态查询中非常有用,避免大量的 if-else 判断。
不等于 ne
ne 方法用于构建不等于条件,对应 SQL 的 <> 或 != 操作符:
@Test
void testNe() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.ne(User::getAge, 25);
List<User> users = userMapper.selectList(wrapper);
// 生成 SQL: SELECT * FROM t_user WHERE age <> 25
}
大于 gt 和 大于等于 ge
@Test
void testGtGe() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// gt: greater than,对应 SQL 的 >
// ge: greater than or equal,对应 SQL 的 >=
wrapper.gt(User::getAge, 20) // age > 20
.ge(User::getAge, 18); // age >= 18
List<User> users = userMapper.selectList(wrapper);
// 生成 SQL: SELECT * FROM t_user WHERE age > 20 AND age >= 18
// 注意:这里只是演示,实际 age > 20 已经隐含了 age >= 18
}
小于 lt 和 小于等于 le
@Test
void testLtLe() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// lt: less than,对应 SQL 的 <
// le: less than or equal,对应 SQL 的 <=
wrapper.lt(User::getAge, 30) // age < 30
.le(User::getAge, 60); // age <= 60
List<User> users = userMapper.selectList(wrapper);
// 生成 SQL: SELECT * FROM t_user WHERE age < 30 AND age <= 60
}
区间查询 between
between 方法用于构建区间查询条件,对应 SQL 的 BETWEEN ... AND ...:
@Test
void testBetween() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 查询年龄在 20 到 30 之间的用户(包含边界值)
wrapper.between(User::getAge, 20, 30);
List<User> users = userMapper.selectList(wrapper);
// 生成 SQL: SELECT * FROM t_user WHERE age BETWEEN 20 AND 30
// 等价于: age >= 20 AND age <= 30
}
注意:between 包含边界值,即 [minValue, maxValue]。
排除区间 notBetween
@Test
void testNotBetween() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 查询年龄不在 20 到 30 之间的用户
wrapper.notBetween(User::getAge, 20, 30);
List<User> users = userMapper.selectList(wrapper);
// 生成 SQL: SELECT * FROM t_user WHERE age NOT BETWEEN 20 AND 30
// 等价于: age < 20 OR age > 30
}
模糊查询
模糊查询是实际开发中最常用的查询方式之一,MyBatis Plus 提供了多种模糊查询方法。
LIKE 全匹配 like
like 方法会在值的两边添加 %,实现全模糊匹配:
@Test
void testLike() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "张");
List<User> users = userMapper.selectList(wrapper);
// 生成 SQL: SELECT * FROM t_user WHERE name LIKE '%张%'
// 可以匹配:张三、小张、张三丰、老张三 等
}
性能提示:LIKE '%xxx%' 无法使用索引,数据量大时会导致全表扫描。如果只需要前缀或后缀匹配,请使用 likeRight 或 likeLeft。
NOT LIKE notLike
@Test
void testNotLike() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.notLike(User::getName, "张");
List<User> users = userMapper.selectList(wrapper);
// 生成 SQL: SELECT * FROM t_user WHERE name NOT LIKE '%张%'
}
左模糊 likeLeft
likeLeft 表示左边模糊,即值出现在字符串的结尾:
@Test
void testLikeLeft() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.likeLeft(User::getName, "三");
List<User> users = userMapper.selectList(wrapper);
// 生成 SQL: SELECT * FROM t_user WHERE name LIKE '%三'
// 可以匹配:张三、李三、王三 等
// 不能匹配:张三丰、三毛 等
}
使用场景:查找以特定字符结尾的数据,如查找所有 .jpg 结尾的文件名。
右模糊 likeRight
likeRight 表示右边模糊,即值出现在字符串的开头:
@Test
void testLikeRight() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.likeRight(User::getName, "张");
List<User> users = userMapper.selectList(wrapper);
// 生成 SQL: SELECT * FROM t_user WHERE name LIKE '张%'
// 可以匹配:张三、张三丰、张小明 等
// 不能匹配:小张、老张 等
}
使用场景:前缀匹配可以使用索引,性能优于全模糊匹配。适合查找以特定前缀开头的数据,如按姓氏查找、按地区代码查找等。
模糊查询对比总结
| 方法 | SQL 效果 | 索引使用 | 典型场景 |
|---|---|---|---|
like | LIKE '%值%' | ❌ 不能 | 全文搜索 |
likeLeft | LIKE '%值' | ❌ 不能 | 后缀匹配 |
likeRight | LIKE '值%' | ✅ 可以 | 前缀匹配、姓氏查找 |
范围查询
IN in
@Test
void testIn() {
List<Integer> ages = Arrays.asList(20, 25, 30);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.in(User::getAge, ages);
List<User> users = userMapper.selectList(wrapper);
}
@Test
void testInArray() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.in(User::getAge, 20, 25, 30);
List<User> users = userMapper.selectList(wrapper);
}
NOT IN notIn
@Test
void testNotIn() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.notIn(User::getAge, 20, 25, 30);
List<User> users = userMapper.selectList(wrapper);
}
IS NULL isNull
@Test
void testIsNull() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.isNull(User::getEmail);
List<User> users = userMapper.selectList(wrapper);
}
IS NOT NULL isNotNull
@Test
void testIsNotNull() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.isNotNull(User::getEmail);
List<User> users = userMapper.selectList(wrapper);
}
分组与排序
GROUP BY groupBy
@Test
void testGroupBy() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("age", "COUNT(*) as count")
.groupBy("age");
List<Map<String, Object>> result = userMapper.selectMaps(wrapper);
result.forEach(System.out::println);
}
ORDER BY orderByAsc / orderByDesc
@Test
void testOrderBy() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByAsc(User::getAge)
.orderByDesc(User::getCreateTime);
List<User> users = userMapper.selectList(wrapper);
}
@Test
void testOrderByWithCondition() {
boolean isAsc = true;
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.orderBy(true, isAsc, User::getAge);
List<User> users = userMapper.selectList(wrapper);
}
HAVING having
@Test
void testHaving() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("age", "COUNT(*) as count")
.groupBy("age")
.having("COUNT(*) > {0}", 1);
List<Map<String, Object>> result = userMapper.selectMaps(wrapper);
}
逻辑连接
AND and
@Test
void testAnd() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "张三")
.and(w -> w.gt(User::getAge, 20).lt(User::getAge, 30));
List<User> users = userMapper.selectList(wrapper);
}
生成的 SQL:
WHERE name = '张三' AND (age > 20 AND age < 30)
OR or
@Test
void testOr() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "张三")
.or()
.eq(User::getName, "李四");
List<User> users = userMapper.selectList(wrapper);
}
@Test
void testOrNested() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "张三")
.or(w -> w.gt(User::getAge, 30).isNotNull(User::getEmail));
List<User> users = userMapper.selectList(wrapper);
}
生成的 SQL:
WHERE name = '张三' OR (age > 30 AND email IS NOT NULL)
NESTED nested
@Test
void testNested() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.nested(w -> w.eq(User::getName, "张三").gt(User::getAge, 20));
List<User> users = userMapper.selectList(wrapper);
}
生成的 SQL:
WHERE (name = '张三' AND age > 20)
其他条件
APPLY apply
用于拼接 SQL 片段:
@Test
void testApply() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.apply("date_format(create_time, '%Y-%m') = {0}", "2024-01");
List<User> users = userMapper.selectList(wrapper);
}
@Test
void testApplyWithDate() {
String startDate = "2024-01-01";
String endDate = "2024-12-31";
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.apply("create_time BETWEEN {0} AND {1}", startDate, endDate);
List<User> users = userMapper.selectList(wrapper);
}
LAST last
在 SQL 末尾追加内容:
@Test
void testLast() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.last("LIMIT 10");
List<User> users = userMapper.selectList(wrapper);
}
EXISTS exists
@Test
void testExists() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.exists("SELECT 1 FROM t_order WHERE t_order.user_id = t_user.id");
List<User> users = userMapper.selectList(wrapper);
}
NOT EXISTS notExists
@Test
void testNotExists() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.notExists("SELECT 1 FROM t_order WHERE t_order.user_id = t_user.id");
List<User> users = userMapper.selectList(wrapper);
}
UpdateWrapper
UpdateWrapper 用于构建更新条件。
基本更新
@Test
void testUpdateWrapper() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getName, "张三")
.set(User::getAge, 26)
.set(User::getEmail, "[email protected]");
int result = userMapper.update(null, wrapper);
}
更新为 NULL
@Test
void testUpdateToNull() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, 1L)
.set(User::getEmail, null);
int result = userMapper.update(null, wrapper);
}
setSql 自定义 SQL
@Test
void testSetSql() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, 1L)
.setSql("age = age + 1");
int result = userMapper.update(null, wrapper);
}
条件判断
条件参数
所有方法都支持条件判断:
@Test
void testCondition() {
String name = null;
Integer minAge = 20;
Integer maxAge = null;
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(name != null, User::getName, name)
.ge(minAge != null, User::getAge, minAge)
.le(maxAge != null, User::getAge, maxAge);
List<User> users = userMapper.selectList(wrapper);
}
动态条件构建
public List<User> searchUsers(String name, Integer minAge, Integer maxAge, String email) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(name), User::getName, name)
.ge(minAge != null, User::getAge, minAge)
.le(maxAge != null, User::getAge, maxAge)
.like(StringUtils.isNotBlank(email), User::getEmail, email);
return userMapper.selectList(wrapper);
}
查询字段选择
select 指定字段
@Test
void testSelectFields() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.select(User::getId, User::getName, User::getAge)
.gt(User::getAge, 20);
List<User> users = userMapper.selectList(wrapper);
}
排除字段
@Test
void testExcludeFields() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select(User.class, info -> !info.getColumn().equals("password"));
List<User> users = userMapper.selectList(wrapper);
}
实战示例
多条件组合查询
public IPage<User> searchUsers(UserQuery query, int pageNum, int pageSize) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName())
.eq(query.getAge() != null, User::getAge, query.getAge())
.between(query.getMinAge() != null && query.getMaxAge() != null,
User::getAge, query.getMinAge(), query.getMaxAge())
.like(StringUtils.isNotBlank(query.getEmail()), User::getEmail, query.getEmail())
.in(query.getStatusList() != null && !query.getStatusList().isEmpty(),
User::getStatus, query.getStatusList())
.orderByDesc(User::getCreateTime);
Page<User> page = new Page<>(pageNum, pageSize);
return userMapper.selectPage(page, wrapper);
}
复杂更新
public void updateUserStatus(Long userId, Integer newStatus, String operator) {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, userId)
.set(User::getStatus, newStatus)
.set(User::getUpdateTime, LocalDateTime.now())
.set(User::getUpdateBy, operator)
.setSql("version = version + 1");
userMapper.update(null, wrapper);
}
统计查询
public Map<String, Object> getUserStatistics() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("COUNT(*) as total",
"AVG(age) as avgAge",
"MAX(age) as maxAge",
"MIN(age) as minAge",
"status");
wrapper.groupBy("status");
List<Map<String, Object>> stats = userMapper.selectMaps(wrapper);
Map<String, Object> result = new HashMap<>();
result.put("statistics", stats);
result.put("total", userMapper.selectCount(null));
return result;
}
小结
本章我们学习了:
- Wrapper 类型:QueryWrapper、LambdaQueryWrapper、UpdateWrapper、LambdaUpdateWrapper
- 比较操作:eq、ne、gt、ge、lt、le、between
- 模糊查询:like、likeLeft、likeRight、notLike
- 范围查询:in、notIn、isNull、isNotNull
- 分组排序:groupBy、orderByAsc、orderByDesc、having
- 逻辑连接:and、or、nested
- 高级用法:apply、last、exists、条件判断