news 2026/4/19 1:39:19

MyBatis-Plus Lambda查询全解析:从QueryWrapper到LambdaQueryChainWrapper的演进与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis-Plus Lambda查询全解析:从QueryWrapper到LambdaQueryChainWrapper的演进与实战

1. 为什么需要Lambda查询?

第一次用MyBatis-Plus的时候,我最头疼的就是写SQL条件。比如要查年龄大于20岁的用户,传统写法是这样的:

QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.gt("age", 20); // 这里的"age"是数据库字段名

这种写法有两个致命问题:第一,字段名是字符串,写错了编译期不会报错;第二,如果数据库字段名改了,所有用到的地方都得手动改。我就在项目里踩过坑,字段名从"age"改成"user_age"后,忘了改某个查询条件,线上直接报错。

Lambda查询就是为了解决这些问题而生的。它用实体类的get方法引用代替字符串,比如:

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.gt(User::getAge, 20); // 编译期就能检查方法是否存在

这样就算数据库字段名改了,只要实体类属性名不变,代码就不用改。而且IDE还能自动补全,再也不用担心拼写错误了。

2. Lambda查询的四种姿势

2.1 LambdaQueryWrapper:最正统的写法

这是官方推荐的标准用法,我在团队里也强制要求使用这种方式。创建方法有三种:

// 方式1:直接new(推荐) LambdaQueryWrapper<User> wrapper1 = new LambdaQueryWrapper<>(); // 方式2:从QueryWrapper转换(历史遗留写法) LambdaQueryWrapper<User> wrapper2 = new QueryWrapper<User>().lambda(); // 方式3:使用Wrappers工具类(简洁但不够直观) LambdaQueryWrapper<User> wrapper3 = Wrappers.lambdaQuery();

实际查询时,可以链式调用多个条件:

List<User> users = new LambdaQueryWrapper<User>() .like(User::getName, "张") // 名字包含"张" .gt(User::getAge, 18) // 年龄大于18 .orderByDesc(User::getCreateTime) // 按创建时间倒序 .list(userMapper); // 最终执行查询

这种写法的优点是清晰明了,适合复杂查询场景。我团队里90%的查询都是用这种方式。

2.2 QueryWrapper.lambda():过渡方案

这是早期版本的写法,现在基本被淘汰了。唯一的使用场景是当你已经有一个QueryWrapper对象,想临时转成Lambda写法:

QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // ...一些非lambda操作 queryWrapper.lambda().eq(User::getName, "张三"); // 中途切换

实际项目中不建议混用,容易造成代码风格混乱。我有次review代码就看到有人前半段用字符串字段名,后半段用Lambda,看得人精神分裂。

2.3 Wrappers.lambdaQuery():工具类写法

这是工具类提供的快捷方式:

LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(); wrapper.eq(User::getRole, "admin");

这种写法虽然简洁,但可读性稍差。适合在方法内部临时使用,如果是公共方法或者复杂查询,还是建议用标准的LambdaQueryWrapper。

2.4 LambdaQueryChainWrapper:最简洁的链式调用

这是MyBatis-Plus 3.x新增的特性,直接把mapper注入到wrapper里:

List<User> users = new LambdaQueryChainWrapper<>(userMapper) .eq(User::getStatus, 1) .like(User::getNickName, "测试") .list();

它的特点是:

  1. 不需要显式调用mapper方法
  2. 链式调用到最后自动执行查询
  3. 适合简单的单表查询

但要注意,复杂查询(比如包含子查询、多表关联)还是用传统的LambdaQueryWrapper更合适。我在处理一个多表join的需求时,强行用ChainWrapper写,结果代码反而更难读了。

3. 实战中的选择策略

3.1 简单查询:LambdaQueryChainWrapper

对于单表的基础CRUD,ChainWrapper能让代码更简洁。比如根据ID查详情:

public User getUserById(Long id) { return new LambdaQueryChainWrapper<>(userMapper) .eq(User::getId, id) .one(); }

但要注意它不支持分页,需要分页时还是得用传统写法:

Page<User> page = new Page<>(1, 10); LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getDeptId, deptId); userMapper.selectPage(page, wrapper);

3.2 复杂查询:LambdaQueryWrapper

当查询条件需要动态拼接时,LambdaQueryWrapper更灵活。比如这个多条件搜索:

public List<User> searchUsers(UserQuery query) { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(query.getName())) { wrapper.like(User::getName, query.getName()); } if (query.getMinAge() != null) { wrapper.ge(User::getAge, query.getMinAge()); } if (query.getMaxAge() != null) { wrapper.le(User::getAge, query.getMaxAge()); } return userMapper.selectList(wrapper); }

3.3 团队规范建议

经过多个项目实践,我总结出以下规范:

  1. 禁止直接使用QueryWrapper(即字符串字段名写法)
  2. 简单查询可以用LambdaQueryChainWrapper
  3. 复杂查询、公共方法必须用LambdaQueryWrapper
  4. 动态SQL条件用LambdaQueryWrapper更清晰
  5. 分页查询只能用LambdaQueryWrapper

4. 性能优化技巧

4.1 避免重复创建Wrapper

我看到很多同事会在循环里创建Wrapper,这是性能杀手:

// 错误示范 for (Long id : idList) { User user = new LambdaQueryChainWrapper<>(userMapper) .eq(User::getId, id) .one(); // ... } // 正确做法 LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); for (Long id : idList) { wrapper.clear(); // 清空上次的条件 wrapper.eq(User::getId, id); User user = userMapper.selectOne(wrapper); // ... }

4.2 选择性查询字段

默认select *会查询所有字段,可以用select指定需要的字段:

List<User> users = new LambdaQueryWrapper<User>() .select(User::getId, User::getName) // 只查id和name .eq(User::getStatus, 1) .list(userMapper);

这个技巧在查询大字段(如text类型的content)时特别有用,能显著减少数据传输量。

4.3 条件优先级控制

遇到or条件时要注意括号问题:

// 查询状态为1,或者(名字包含张且年龄大于18) wrapper.eq(User::getStatus, 1) .or(qw -> qw.like(User::getName, "张").gt(User::getAge, 18));

生成的SQL会是:status = 1 OR (name LIKE '%张%' AND age > 18)。如果不加or嵌套,条件优先级会出错。

5. 常见坑点记录

5.1 日期范围查询

处理日期时要特别注意时区问题:

// 查询今天创建的用户 LocalDateTime start = LocalDate.now().atStartOfDay(); LocalDateTime end = LocalDate.now().plusDays(1).atStartOfDay(); wrapper.between(User::getCreateTime, start, end);

我遇到过因为服务器时区设置不同,导致本地测试正常但线上查询结果不对的情况。建议所有日期处理都明确指定时区。

5.2 null值处理

eq(null)会被忽略,要用isNull:

// 查询email为null的记录 wrapper.isNull(User::getEmail); // 查询email不为null的记录 wrapper.isNotNull(User::getEmail);

5.3 批量操作

批量insert时,如果用了ChainWrapper要注意:

// 错误:这样只会插入最后一条 new LambdaQueryChainWrapper<>(userMapper) .set(User::getName, "name1").insert() .set(User::getName, "name2").insert(); // 正确:每次都要新建wrapper new LambdaQueryChainWrapper<>(userMapper) .set(User::getName, "name1").insert(); new LambdaQueryChainWrapper<>(userMapper) .set(User::getName, "name2").insert();

6. 复杂查询示例

6.1 嵌套查询

查询部门下所有用户:

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.inSql(User::getDeptId, "SELECT id FROM dept WHERE parent_id = " + parentId);

6.2 联表查询

虽然MyBatis-Plus主打单表操作,但也能支持联表:

@Select("SELECT u.* FROM user u LEFT JOIN dept d ON u.dept_id=d.id ${ew.customSqlSegment}") List<User> selectUserWithDept(@Param(Constants.WRAPPER) LambdaQueryWrapper<User> wrapper); // 调用时 List<User> users = userMapper.selectUserWithDepp( new LambdaQueryWrapper<User>() .eq(User::getStatus, 1) .like(User::getName, "张") );

6.3 动态排序

前端传排序字段时,可以这样安全处理:

public List<User> getUsers(String sortField, boolean asc) { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); // 防止SQL注入,只允许排序实体类有的字段 try { Method method = User.class.getMethod("get" + sortField.substring(0,1).toUpperCase() + sortField.substring(1)); if (asc) { wrapper.orderByAsc(User::getMethod(method)); } else { wrapper.orderByDesc(User::getMethod(method)); } } catch (Exception e) { // 默认排序 wrapper.orderByDesc(User::getCreateTime); } return userMapper.selectList(wrapper); }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/19 1:38:17

【算法日记】Day 19 动态规划专题——状态压缩DP(二)

Abstract&#xff1a;#动态规划 #状压DP #异或 1. 题目 题目&#xff1a;LeetCode 473. 火柴拼正方形核心思路&#xff1a;将每根火柴看作一个物品&#xff0c;用二进制状态s表示火柴的使用情况。设边长为len&#xff0c;定义dp[s]表示状态s下&#xff0c;当前正在拼的边上已…

作者头像 李华
网站建设 2026/4/19 1:31:33

MATLAB min函数进阶:从基础语法到多维度数据处理的实战解析

1. MATLAB min函数基础语法解析 第一次接触MATLAB的min函数时&#xff0c;我习惯性地以为它就是个简单的找最小值工具。直到在数据分析项目中踩了几个坑才发现&#xff0c;这个看似简单的函数藏着不少门道。先来看最基本的用法&#xff1a; A [3 7 2 8 5]; min_value min(A) …

作者头像 李华