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

相关推荐
小马哥程序开发1 小时前
[点赞收藏免费领取 · 项目源码]57105基于Spring Boot的充电桩管理系统的设计与实现
java·spring boot·源码·课程设计·毕设·大作业·课设
卓怡学长4 小时前
w202springboot基于B_S架构的图书信息管理系统
java·spring boot·spring·intellij-idea
走到天涯海角4 小时前
用springBoot写几个简单的接口
java·spring boot·intellij-idea
xcl09254 小时前
西安同城货运系统源码开发实战 核心功能实现与部署全指南
java·spring boot
ym hyd 1115 小时前
慢性病精细化管理平台源码 Java+SpringBoot+Vue3 前后分离
java·vue.js·spring boot·毕设
RuoyiOffice5 小时前
SpringBoot3+Vue3 低代码业务表单:从拖拽设计、独立建表到审批与数据查询
spring boot·vue3·数据建模·flowable·版本管理·低代码表单·ruoyi office
需要8265 小时前
MySQL MVCC 与事务隔离级别:从一条 update 看版本链
java·数据库·spring boot·mysql·spring cloud
RuoyiOffice5 小时前
SpringBoot3 企业薪酬核算:考勤、绩效与薪资规则如何算出一张可解释的工资单
spring boot·vue3·规则引擎·hrm·ruoyi office·薪酬核算·薪资试算
阿狗童鞋7 小时前
SpringBoot微服务实战指南
spring boot·后端·微服务
程序猿乐锅7 小时前
【黑马点评 | 第九篇】秒杀优化-异步实现
java·spring boot·redis·后端·spring·中间件·mybatis