Mybatis Plus中 LambdaQueryWrapper、QueryWrapper 区别对比

在 MyBatis-Plus (MP) 中,QueryWrapperLambdaQueryWrapper 都是用于构建查询条件的核心工具类。它们的功能完全一致,但使用方式、类型安全性和可维护性有显著区别。

以下是详细的对比与完整示例。


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 的条件构造器。

相关推荐
干到60岁退休的码农1 小时前
13.构建登录接口响应数据
java·spring boot·mybatis
水巷石子1 小时前
学习langChain4j的第二天,体验springBoot中的starter
java·spring boot·学习·spring·langchain4j
摇滚侠2 小时前
《Spring Boot 3:高级与架构设计》第 1 章 BeanDefinitionRegistry 阅读笔记 3
spring boot·笔记·后端
zhougl9962 小时前
Dockerfile实战教程
java·开发语言·spring boot
计算机毕设定制辅导-无忧学长2 小时前
《基于Spring Boot传承之光非遗陶瓷烧造产品交易平台的设计与实现》
java·vue.js·spring boot·后端·毕业设计
计算机学姐2 小时前
基于SpringBoot的旅游系统的设计与实现
java·vue.js·spring boot·后端·spring·java-ee·旅游
卷心菜的学习路2 小时前
Spring Boot 多数据源落地:AbstractRoutingDataSource + 注解切面(附源码)
java·spring boot·后端
武哥聊编程3 小时前
【AI实战项目】基于SpringAI+Springboot+Vue的AI智能简历优化助手
vue.js·人工智能·spring boot
卓怡学长4 小时前
w157基于springboot北部湾地区助农平台
java·spring boot·spring·maven·intellij-idea