QueryWrapper
是 MyBatis-Plus 提供的核心条件构造器(Wrapper),用于构建复杂的 SQL 查询条件,无需手写 SQL 即可实现 WHERE
、AND
、OR
、GROUP BY
、HAVING
、ORDER BY
等功能。它极大提升了开发效率,是 MyBatis-Plus 最常用、最强大的功能之一。
一、核心概念
1. 什么是 QueryWrapper
?
QueryWrapper<T>
是 MyBatis-Plus 提供的泛型条件构造器,用于构建SELECT
查询的WHERE
条件。- 它通过链式调用方式,将 Java 方法调用自动转换为 SQL 条件片段。
- 支持:
- 字段比较(
eq
,ne
,gt
,ge
,lt
,le
) - 逻辑运算(
and
,or
) - 模糊查询(
like
,notLike
) - 范围查询(
between
,notBetween
) - 空值判断(
isNull
,isNotNull
) - 分组与聚合(
groupBy
,having
) - 排序(
orderByAsc
,orderByDesc
) - 自定义 SQL 片段(
apply
) - 子查询支持
- 字段比较(
2. 相关 Wrapper 类
类名 | 用途 |
---|---|
QueryWrapper<T> |
查询条件构造器(最常用) |
UpdateWrapper<T> |
更新条件构造器 |
LambdaQueryWrapper<T> |
Lambda 版本,类型安全,避免字段名硬编码 |
QueryChainWrapper<T> |
链式查询调用(如 lambda().eq(...).list() ) |
二、操作步骤(非常详细)
步骤 1:准备实体类与 Mapper
@Data
@TableName("user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
private LocalDateTime createTime;
private Integer status;
}
@Mapper
public interface UserMapper extends BaseMapper<User> {}
步骤 2:注入 Mapper 并使用 QueryWrapper
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
步骤 3:基础查询(等于、不等于、范围)
示例 1:查询年龄等于 25 的用户
public List<User> getUsersByAge(Integer age) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("age", age); // WHERE age = ?
return userMapper.selectList(wrapper);
}
示例 2:查询年龄在 18 到 30 之间的用户
public List<User> getUsersByAgeRange() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 18, 30); // WHERE age BETWEEN ? AND ?
return userMapper.selectList(wrapper);
}
示例 3:查询姓名不为空且状态为 1 的用户
public List<User> getValidUsers() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name") // WHERE name IS NOT NULL
.eq("status", 1); // AND status = 1
return userMapper.selectList(wrapper);
}
步骤 4:模糊查询
示例 4:姓名包含“张”且邮箱以 “@example.com” 结尾
public List<User> getUsersByNameAndEmail() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name", "张") // WHERE name LIKE '%张%'
.likeRight("email", "@example.com"); // AND email LIKE '@example.com%' (左匹配)
return userMapper.selectList(wrapper);
}
🔍
likeLeft
:右模糊(%value
)
likeRight
:左模糊(value%
)
步骤 5:逻辑运算(AND / OR)
示例 5:复杂逻辑 (age > 20 AND status = 1) OR name LIKE '王%'
public List<User> getComplexUsers() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.nested(qw -> qw.gt("age", 20).eq("status", 1)) // (age > 20 AND status = 1)
.or()
.likeLeft("name", "王"); // OR name LIKE '王%'
return userMapper.selectList(wrapper);
}
✅
nested(...)
用于创建括号组。
步骤 6:分组与聚合
示例 6:按状态分组,统计每组人数,且人数 > 2
public List<Map<String, Object>> getUserCountByStatus() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("status", "count(*) as count") // SELECT status, count(*)
.groupBy("status") // GROUP BY status
.having("count(*) > {0}", 2); // HAVING count(*) > 2
return userMapper.selectMaps(wrapper);
}
⚠️ 使用
selectMaps
获取Map
结果。
步骤 7:排序
示例 7:按年龄降序,创建时间升序
public List<User> getUsersSorted() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("age") // ORDER BY age DESC
.orderByAsc("create_time"); // , create_time ASC
return userMapper.selectList(wrapper);
}
步骤 8:使用 Lambda 版本(推荐)
避免字段名硬编码,类型安全。
public List<User> getUsersByAgeLambda(Integer age) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getAge, age) // 字段引用
.like(User::getName, "张")
.orderByDesc(User::getCreateTime);
return userMapper.selectList(wrapper);
}
✅ 强烈推荐使用
LambdaQueryWrapper
,防止字段名拼写错误。
步骤 9:自定义 SQL 片段(apply
)
示例 9:使用数据库函数
wrapper.apply("date_format(create_time, '%Y-%m') = {0}", "2025-08");
生成 SQL:
WHERE date_format(create_time, '%Y-%m') = '2025-08'
⚠️
apply
不做 SQL 注入检查,慎用用户输入!
三、常见错误与解决方案
错误现象 | 原因 | 解决方案 |
---|---|---|
查询无结果或条件未生效 | 字段名写错(如 user_name 写成 userName ) |
使用 LambdaQueryWrapper |
报错 Unknown column |
数据库字段与实体属性不一致,且未用 @TableField 映射 |
添加 @TableField("db_column_name") |
OR 逻辑混乱 |
未使用 nested 分组 |
用 nested 包裹 AND 条件 |
分页失效 | 未配合 Page 对象使用 |
使用 selectPage(page, wrapper) |
apply 导致 SQL 注入 |
拼接用户输入 | 使用 {0} 占位符传参 |
四、注意事项
字段名大小写敏感性
MySQL 默认不区分,但建议保持一致。使用@TableField
明确映射。null
值处理
eq("name", null)
生成name = NULL
,应使用isNull("name")
。避免在 Wrapper 中拼接 SQL
优先使用内置方法,apply
仅用于特殊场景。分页查询需用
Page
Page<User> page = new Page<>(1, 10); userMapper.selectPage(page, wrapper);
Wrapper 不支持
JOIN
多表关联需使用@Select
注解或 XML。
五、使用技巧
技巧 1:条件动态拼接(空值自动忽略)
wrapper.eq(age != null, "age", age)
.like(StringUtils.hasText(name), "name", name);
✅ 第一个参数为
boolean
,true
时才添加条件。
技巧 2:链式调用(QueryChainWrapper)
@Autowired
private UserMapper userMapper;
// 启用 chain 模式
public List<User> getUsersChained() {
return userMapper.selectList(
new QueryWrapper<User>()
.eq("status", 1)
.orderByDesc("create_time")
);
}
// 或使用 lambdaChain
public List<User> getUsersLambdaChain() {
return new LambdaQueryChainWrapper<>(userMapper)
.eq(User::getStatus, 1)
.like(User::getName, "张")
.list();
}
技巧 3:子查询支持
wrapper.in("id",
new QueryWrapper<User>().select("id").eq("status", 0).getSqlSegment()
);
六、最佳实践
实践 | 说明 |
---|---|
✅ 优先使用 LambdaQueryWrapper |
类型安全,避免硬编码 |
✅ 动态条件使用 condition 参数 |
如 .eq(age != null, "age", age) |
✅ 复杂查询拆分逻辑 | 避免单个 Wrapper 过于庞大 |
✅ 分页使用 IPage |
结合 PageHelper 或 MP 自带分页 |
✅ 日志开启 SQL 输出 |
便于调试生成的 SQL |
✅ 避免 SELECT * |
使用 select() 指定字段 |
七、性能优化
优化点 | 说明 |
---|---|
索引优化 | 确保 WHERE 、ORDER BY 字段有索引 |
避免全表扫描 | LIKE '%value%' 无法使用索引,尽量用 LIKE 'value%' |
减少字段查询 | select("id", "name") 替代 SELECT * |
合理分页 | 避免 OFFSET 过大,可用 WHERE id > lastId 实现游标分页 |
缓存结果 | 频繁查询可结合 Redis 缓存 |
SQL 复用 | 相同条件可复用 Wrapper 实例(注意线程安全) |
八、总结
项目 | 内容 |
---|---|
核心类 | QueryWrapper<T> , LambdaQueryWrapper<T> |
核心优势 | 链式调用、类型安全、免 SQL |
推荐用法 | LambdaQueryWrapper + condition 动态拼接 |
适用场景 | 单表复杂查询、动态条件 |
性能关键 | 索引、避免全表扫描、合理分页 |
✅ 一句话总结:
QueryWrapper
(尤其是 LambdaQueryWrapper
)是 MyBatis-Plus 实现高效、安全、可维护查询的核心工具,掌握其用法是使用 MP 的必备技能。