MyBatis-Plus LambdaQueryWrapper实战:告别手写SQL

背景

做Java后端的应该都用过MyBatis-Plus,但很多人还是习惯写XML或者用QueryWrapper拼字符串。字符串拼字段名有个问题:字段名写错了编译器不报错,重构时IDE也不会帮你改,只能等单元测试或者线上报错才能发现。

LambdaQueryWrapper解决了这个问题。它用Java的Lambda表达式引用字段,编译期就能检查字段名是否正确。用了一年多,分享一些高频用法和踩坑经验。

基础用法

假设有个用户表:

kotlin 复制代码
@Data
@TableName("sys_user")
public class User {
    private Long id;
    private String username;
    private String email;
    private Integer status;    // 0禁用 1启用
    private Long deptId;
    private LocalDateTime createTime;
}

等值查询

perl 复制代码
// 老写法:字符串拼字段
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);  // 写错不报错
​
// Lambda写法:编译期检查
LambdaQueryWrapper<User> lambda = new LambdaQueryWrapper<>();
lambda.eq(User::getUsername, username);  // 字段名错了编译直接红

多条件组合

sql 复制代码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
       .like(User::getUsername, keyword)
       .ge(User::getCreateTime, startTime)
       .le(User::getCreateTime, endTime)
       .orderByDesc(User::getCreateTime);
​
List<User> users = userMapper.selectList(wrapper);

条件动态拼接

这是业务代码里最常见的场景------前端传了哪些参数就拼哪些条件:

css 复制代码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
       .eq(StrUtil.isNotBlank(dto.getEmail()), User::getEmail, dto.getEmail())
       .like(StrUtil.isNotBlank(dto.getUsername()), User::getUsername, dto.getUsername())
       .eq(dto.getDeptId() != null, User::getDeptId, dto.getDeptId())
       .ge(dto.getStartTime() != null, User::getCreateTime, dto.getStartTime())
       .le(dto.getEndTime() != null, User::getCreateTime, dto.getEndTime());

eq(condition, field, value) 第一个参数是布尔条件,为 false 时自动跳过这个条件。不用写一堆 if-else,一个链式调用搞定。

IN 查询 + 分页

ini 复制代码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.in(CollectionUtil.isNotEmpty(deptIds), User::getDeptId, deptIds)
       .eq(User::getStatus, 1);
​
Page<User> page = new Page<>(pageNum, pageSize);
userMapper.selectPage(page, wrapper);

只查部分字段

有时候不需要查全部字段,用 select 指定:

ini 复制代码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.select(User::getId, User::getUsername, User::getEmail)
       .eq(User::getDeptId, deptId);
​
List<User> users = userMapper.selectList(wrapper);

子查询

ini 复制代码
// 查有审批记录的用户
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.inSql(User::getId, "SELECT DISTINCT user_id FROM approval_record WHERE status = 'pending'");

复杂条件:AND/OR嵌套

真实业务里经常遇到这种需求:"查询(部门A 且 状态启用)或(部门B 且 角色为管理员)的用户"。Lambda写法:

perl 复制代码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.and(w -> w.eq(User::getDeptId, 1L).eq(User::getStatus, 1))
       .or(w -> w.eq(User::getDeptId, 2L).eq(User::getRole, "ADMIN"));

生成的SQL:

ini 复制代码
SELECT * FROM sys_user 
WHERE (dept_id = 1 AND status = 1) 
   OR (dept_id = 2 AND role = 'ADMIN')

分组和聚合

sql 复制代码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.select(User::getDeptId, User::getStatus)
       .groupBy(User::getDeptId, User::getStatus)
       .having("COUNT(*) > {0}", 5);

踩过的两个坑

1. updateById 只更新非null字段

这个坑前面文章也提过。MyBatis-Plus默认策略是 FieldStrategy.NOT_NULL,null字段不更新。如果你创建了一个只含两个字段的Entity去调 updateById,其他字段不会被更新------因为null被跳过了。

解决方案:

ini 复制代码
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Integer status;

或者用 LambdaUpdateWrapper 明确指定:

ini 复制代码
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(User::getStatus, 0)
             .eq(User::getId, userId);
userMapper.update(null, updateWrapper);

推荐后者,语义更明确。

2. like 默认不加百分号

like 方法不会自动加 %,如果不手动加就是精确匹配:

less 复制代码
// 错误:相当于 = 'xxx'
wrapper.like(User::getUsername, keyword);
​
// 正确:模糊匹配
wrapper.like(User::getUsername, "%" + keyword + "%");

用 Hutool 的 StrUtil.format("%{}%", keyword) 更简洁。

什么时候不用Lambda

  • 复杂关联查询:多表 JOIN、复杂子查询,直接用 XML 写 SQL,Lambda 不适合
  • 动态排序:orderBy 的字段来自前端传参时,只能用字符串
  • 原生函数:聚合、窗口函数等 Lambda 不支持

总结

LambdaQueryWrapper 的三个核心优势:

  1. 编译期检查 --- 字段名写错直接红,不怕重构
  2. 代码可读性好 --- User::getUsername"username" 直观
  3. 条件动态拼接 --- eq(condition, field, value) 一行搞定,告别 if-else

日常开发中 80% 的查询用它就够了,剩下 20% 复杂场景老老实实写 XML。


我正在用"一个人+AI"的模式开发微信小程序"面狮狮",微信搜索即可体验。更多实战经验:gitee.com/yao113088/j...

相关推荐
街头小霸王6261 小时前
微服务项目common配置文件模板
后端
Conan在掘金1 小时前
ArkTS 进阶之道(17):@Extend 专属属性扩展边界——为啥能收 fontSize 专属属性
后端
霸道流氓气质2 小时前
SpringBoot中事务内同步处理 + 事务后异步调用外部系统的通用模式示例
java·spring boot·后端
AskHarries3 小时前
用户埋点怎么设计
后端
极客悟道4 小时前
VS Code + JetTUI 开发 Spring Boot项目,教程来了
后端
Yeauty4 小时前
自建 HLS 第一问:fMP4 还是 TS?用 Rust 在进程内把两种都跑出来
开发语言·后端·rust
Zane19944 小时前
copy 和 deepcopy 到底在拷贝什么?一文讲清赋值、浅拷贝、深拷贝的引用关系
后端·python
zhiSiBuYu05174 小时前
Flask 路由新手入门与实战指南
后端·python·flask
元界metalite4 小时前
禁止 Feign!我们为什么自研 InternalServiceClient
后端