Mybatis Plus中 LambdaQueryWrapper、QueryWrapper 区别对比

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

相关推荐
我是人✓12 小时前
SpringBoot项目的五种创建方式
java·spring boot·后端
古法老登瞎折腾15 小时前
大数据智能运维实战(一):机器和任务数据都有了,如何用 MCP 搭建运维助手?
大数据·运维·spring boot·spring ai·mcp
企业数字化笔记16 小时前
多租户配额怎么控制?用量口径、并发预留与超额处理的实现方法
java·数据库·spring boot
WeiXin_DZbishe17 小时前
基于springboot装修风格选择系统的设计与开发-计算机毕设【课程设计】77964
java·vue.js·spring boot·后端·python·php·课程设计
十年Java程序媛19 小时前
Java 多线程基础与锁机制详解|进程线程、锁升级、synchronized 与 Lock 对比
java·开发语言·spring boot
Q264336502321 小时前
【有源码】基于SpringBoot+Vue的居民健康体检与慢病干预管理系统-面向智慧医疗的体检预约与健康知识服务平台
java·vue.js·spring boot·mysql·毕业设计·javaweb·课程设计
撸串-研究生21 小时前
基于Spring Boot的智慧社区垃圾分类与回收系统的设计与实现【附源码】
spring boot·后端·分类·垃圾分类·社区管理
ly76891 天前
PostgreSQL 流复制与高可用切换:主从延迟、同步提交与 Spring Boot 读写分离的边界
数据库·spring boot·postgresql·读写分离·流复制·高可用切换
技术小结-李爽1 天前
【工具】怎么在终端中启动springboot项目?
java·spring boot·后端·maven
Msshu1231 天前
PD快充诱骗取电芯片工作原理,XSP17诱骗取电5V9V12V15V20V功能测试
spring boot·spring cloud·sentinel·java-rocketmq·java-zookeeper·java-consul·java-activemq