在 MyBatis-Plus (MP) 中,QueryWrapper 和 LambdaQueryWrapper 都是用于构建查询条件的核心工具类。它们的功能完全一致,但使用方式、类型安全性和可维护性有显著区别。
以下是详细的对比与完整示例。
1. 核心区别对比
| 特性 | QueryWrapper | LambdaQueryWrapper |
|---|---|---|
| 字段引用方式 | ❌ 字符串硬编码 ("user_name") |
✅ Lambda 表达式 (User::getUserName) |
| 类型安全 | ❌ 无编译时检查,拼写错误运行时才报错 | ✅ 编译时检查,重构友好,IDE 自动提示 |
| 代码可读性 | ⚠️ 一般,需记忆数据库列名 | ✅ 优秀,直接使用实体类属性名 |
| 重构风险 | 🔴 高(修改字段名后需全局搜索替换字符串) | 🟢 低(IDE 自动同步更新引用) |
| 性能 | 🟢 略快(直接拼接 SQL) | 🟡 极微小开销(解析 Lambda),实际开发可忽略 |
| 推荐程度 | ⭐⭐ 仅动态/复杂场景使用 | ⭐⭐⭐⭐⭐ 日常开发首选 |
💡 一句话总结 :能用 LambdaQueryWrapper 就用它;只有在字段名是动态传入的字符串变量 时,才退回到 QueryWrapper。
2. 前置准备
java
// 实体类
@Data
@TableName("t_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private Integer age;
private String email;
private LocalDateTime createTime;
}
// Mapper
@Mapper
public interface UserMapper extends BaseMapper<User> {}
3. 完整示例对比
场景:查询年龄 > 20 且用户名包含 "张" 的用户,按创建时间倒序
❌ QueryWrapper(字符串方式)
java
@Test
void testQueryWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("user_name", "张") // ⚠️ 必须写数据库列名
.gt("age", 20) // ⚠️ 拼写错误不会编译报错
.orderByDesc("create_time"); // ⚠️ 重构时容易遗漏
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
✅ LambdaQueryWrapper(推荐方式)
java
@Test
void testLambdaQueryWrapper() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getUserName, "张") // ✅ IDE 自动补全 + 编译检查
.gt(User::getAge, 20) // ✅ 字段重命名后自动跟随
.orderByDesc(User::getCreateTime); // ✅ 类型安全
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
4. LambdaQueryWrapper 高级用法
4.1 条件构造(避免大量 if-else)
java
String name = "张";
Integer minAge = 20;
Integer maxAge = null; // 可能为空
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.like(StrUtil.isNotBlank(name), User::getUserName, name) // condition=true 才拼接
.ge(minAge != null, User::getAge, minAge)
.le(maxAge != null, User::getAge, maxAge) // maxAge为null,此条件跳过
.orderByDesc(User::getCreateTime);
4.2 OR / AND 嵌套
java
// WHERE (user_name LIKE '%张%' AND age > 20) OR email = 'test@qq.com'
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.and(w -> w.like(User::getUserName, "张")
.gt(User::getAge, 20))
.or(w -> w.eq(User::getEmail, "test@qq.com"));
4.3 链式调用(LambdaQueryChainWrapper)
java
// 无需手动 new Wrapper,更简洁
List<User> users = userMapper.selectList(
new LambdaQueryChainWrapper<>(userMapper)
.like(User::getUserName, "张")
.gt(User::getAge, 20)
.list() // 直接返回结果
);
// Service 层更常用(ServiceImpl 自带 lambdaQuery())
List<User> users = userService.lambdaQuery()
.like(User::getUserName, "张")
.gt(User::getAge, 20)
.list();
4.4 IN / BETWEEN / IS NULL 等常用操作
java
new LambdaQueryWrapper<User>()
.in(User::getAge, Arrays.asList(20, 25, 30))
.between(User::getCreateTime,
LocalDateTime.of(2024,1,1,0,0,0),
LocalDateTime.of(2024,12,31,23,59,59))
.isNull(User::getEmail)
.select(User::getId, User::getUserName); // 只查指定列
5. 什么时候必须用 QueryWrapper?
当字段名是运行时动态决定的,Lambda 无法表达时:
java
// 前端传入排序字段 "age" 或 "create_time"
String sortField = request.getSortBy(); // 动态字符串
String sortOrder = request.getOrder(); // "asc" / "desc"
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 20);
// ⚠️ 这里只能用字符串版本,Lambda 无法接收变量
if ("asc".equalsIgnoreCase(sortOrder)) {
wrapper.orderByAsc(sortField);
} else {
wrapper.orderByDesc(sortField);
}
⚠️ 安全警告 :动态字段名务必做白名单校验,防止 SQL 注入!
6. 最佳实践总结
| 建议 | 说明 |
|---|---|
✅ 默认使用 LambdaQueryWrapper |
类型安全、重构友好 |
✅ Service 层用 lambdaQuery() |
链式调用最简洁 |
✅ 善用 condition 参数 |
消除 if-else 拼接 |
⚠️ 动态字段退回 QueryWrapper |
做好白名单防注入 |
| ❌ 不要在 Lambda 中调用方法 | User::getUserName 只能是 getter 引用,不能是 User::getName().trim() |
| ❌ 不要混用两种 Wrapper | 保持代码风格统一 |
掌握以上内容和示例,即可在实际项目中灵活、安全地使用 MyBatis-Plus 的条件构造器。