MyBatisPlus条件构造器避坑指南:为什么你的eq查询有时会漏数据?
MyBatisPlus条件构造器避坑指南:为什么你的eq查询有时会漏数据?
在Java持久层框架中,MyBatisPlus以其简洁的API和强大的功能深受开发者喜爱。然而,在实际使用条件构造器进行等值查询(eq)时,不少开发者都遇到过查询结果与预期不符的情况——要么漏掉了本该匹配的数据,要么返回了意料之外的结果集。这些问题往往源于对eq方法底层逻辑的误解或对细节处理的疏忽。
本文将深入剖析MyBatisPlus条件构造器中eq查询的七个典型陷阱,通过真实案例演示如何正确构建查询条件。无论你是刚接触MyBatisPlus的新手,还是已经使用一段时间的中级开发者,这些经验总结都能帮助你写出更健壮的数据库查询代码。
1. null值处理的隐藏逻辑
很多开发者在使用eq方法时,会想当然地认为eq("name", null)等价于SQL中的name IS NULL。实际上,MyBatisPlus对null值有特殊的处理逻辑:
// 常见的错误写法 queryWrapper.eq("name", null); // 实际生成的SQL: WHERE (name = null) // 这会导致查询不到任何结果正确做法是使用isNull方法:
queryWrapper.isNull("name"); // 生成的SQL: WHERE (name IS NULL)对于可能为null的查询条件,推荐使用条件构造:
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(name != null, User::getName, name) .isNull(name == null, User::getName);2. 链式调用的条件叠加
MyBatisPlus的条件构造器支持链式调用,但这种便利性也可能导致意外的条件叠加:
// 危险示例 public List<User> findUsers(String name, Integer age) { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); if (name != null) { wrapper.eq(User::getName, name); } if (age != null) { wrapper.eq(User::getAge, age); } return userDao.selectList(wrapper); }当name和age都为null时,这个方法会返回表中的所有记录,可能引发性能问题。更安全的做法是:
public List<User> findUsersSafely(String name, Integer age) { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(name != null, User::getName, name) .eq(age != null, User::getAge, age); return userDao.selectList(wrapper); }3. selectOne与selectList的误用
selectOne方法常被误用于"查询单个记录"的场景,但实际上它的语义是"查询结果应该只有一条记录":
// 错误示例:即使有多条匹配记录也只会返回第一条 User user = userDao.selectOne(wrapper.eq(User::getStatus, 1));正确的做法取决于业务需求:
| 需求场景 | 推荐方法 | 说明 |
|---|---|---|
| 确保唯一记录 | selectOne | 配合唯一条件使用 |
| 获取第一条匹配记录 | selectList + limit(1) | 更明确的意图表达 |
| 获取所有匹配记录 | selectList | 最通用的查询方式 |
对于需要确保唯一性的查询,应该添加唯一性条件:
User user = userDao.selectOne( wrapper.eq(User::getUsername, "admin") .eq(User::getTenantId, 1001) );4. 多条件组合的优先级问题
当构建包含多个条件的复杂查询时,条件的优先级可能影响最终结果:
wrapper.eq(User::getType, 1) .or() .eq(User::getStatus, 1) .eq(User::getVisible, true);生成的SQL可能是:
WHERE (type = 1 OR status = 1 AND visible = true)这往往不是开发者想要的。正确的做法是使用括号明确优先级:
wrapper.and(w -> w.eq(User::getType, 1).or().eq(User::getStatus, 1)) .eq(User::getVisible, true);对应的SQL:
WHERE ((type = 1 OR status = 1) AND visible = true)5. 字段名映射的坑
MyBatisPlus支持多种字段名映射方式,但这也可能导致一些混淆:
// 使用Lambda表达式 wrapper.eq(User::getName, "John"); // 使用字符串字段名 wrapper.eq("name", "John"); // 使用数据库列名 wrapper.eq("user_name", "John");常见问题包括:
- 实体类字段名与数据库列名不一致时未使用@TableField注解
- 使用字符串字段名时拼写错误
- Lambda表达式引用错误的方法
最佳实践:
- 优先使用Lambda表达式,编译时检查字段名
- 保持实体类字段命名与数据库一致
- 必要时使用@TableField明确映射关系
6. 与聚合查询的冲突
当同时使用eq条件和聚合函数时,结果可能出乎意料:
// 错误示例 wrapper.select("count(*) as count") .eq(User::getDepartment, "IT");这会导致只返回计数结果而丢失原始记录。正确的做法是:
// 方案1:分开查询 Long count = userDao.selectCount( wrapper.eq(User::getDepartment, "IT") ); // 方案2:使用groupBy wrapper.select("department, count(*) as count") .groupBy("department") .eq(User::getCompany, "ACME");7. 性能陷阱与优化建议
看似简单的eq查询也可能引发性能问题:
常见性能陷阱:
- 对未加索引的字段频繁查询
- 过多无意义的条件组合
- 未合理使用缓存
优化建议:
- 为常用查询字段添加数据库索引
- 使用
eq(condition, column, value)避免不必要的条件 - 考虑使用MyBatisPlus的二级缓存
- 对固定条件查询结果进行缓存
// 使用缓存示例 @Cacheable(value = "users", key = "#name") public User findByUsername(String name) { return userDao.selectOne( new LambdaQueryWrapper<User>() .eq(User::getUsername, name) ); }在实际项目中,我曾遇到一个典型案例:系统在用户登录时偶尔会返回错误用户。经过排查,发现是因为开发者在构建查询条件时,没有处理用户名的前后空格,导致eq查询匹配失败。解决方案很简单:
wrapper.eq(User::getUsername, username.trim());这个小改动解决了困扰团队数周的随机登录问题。这提醒我们,在使用eq查询时,必须考虑数据的一致性和清洗问题。
