MyBatis-Plus 中 and() 与 or() 的嵌套组合:构建复杂查询条件的实战解析
1. 初识MyBatis-Plus的条件构造器
第一次接触MyBatis-Plus的QueryWrapper时,我就被它的简洁语法惊艳到了。相比传统的MyBatis需要手写XML SQL语句,QueryWrapper通过链式调用就能构建复杂的查询条件。特别是and()和or()这两个方法,简直就是处理多条件查询的神器。
记得我刚接手一个后台管理系统时,需要实现用户的多条件筛选功能。比如要查询"年龄大于25岁且(姓名包含张或手机号以138开头)"的用户。如果用传统方式,这个SQL写起来会很复杂,而QueryWrapper只需要几行代码就能搞定:
QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.gt("age", 25) .and(qw -> qw.like("name", "张").or().like("phone", "138%"));这段代码生成的SQL正是我们想要的:WHERE age > 25 AND (name LIKE '%张%' OR phone LIKE '138%')。这种Lambda表达式的写法不仅直观,还能自动处理括号的嵌套关系,大大减少了出错的可能性。
2. 理解and()和or()的基本用法
2.1 and()方法的本质
and()在QueryWrapper中表示逻辑"与"的关系。它有两种使用方式:
- 隐式调用:直接链式调用条件方法时,默认就是and关系
wrapper.eq("status", 1).ge("create_time", "2023-01-01"); // 等同于 WHERE status = 1 AND create_time >= '2023-01-01'- 显式调用:通过and()方法包裹条件组
wrapper.eq("dept_id", 10) .and(qw -> qw.gt("salary", 5000).lt("salary", 10000)); // 等同于 WHERE dept_id = 10 AND (salary > 5000 AND salary < 10000)实际项目中我发现,显式使用and()的主要场景是需要对一组条件整体进行AND运算时,特别是这组条件本身又包含OR关系的时候。
2.2 or()方法的妙用
or()表示逻辑"或"的关系,同样有两种使用方式:
- 简单条件OR连接
wrapper.eq("type", 1).or().eq("type", 2); // WHERE type = 1 OR type = 2- 复杂条件OR组合
wrapper.eq("status", 1) .or(qw -> qw.eq("status", 2).ge("create_time", "2023-01-01")); // WHERE status = 1 OR (status = 2 AND create_time >= '2023-01-01')在商品筛选功能中,我经常用or()来实现"满足A条件或者同时满足B和C条件"这类需求。比如查询"特价商品或(新品且库存充足)"的商品列表。
3. 嵌套组合的实战技巧
3.1 实现A AND (B OR C)结构
这是最常见的嵌套需求之一。假设我们要查询"部门ID为10且(职称为高级或者入职满3年)"的员工:
QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.eq("dept_id", 10) .and(qw -> qw.eq("title", "高级").or().ge("hire_years", 3));这里的关键点是:
- 先用eq()设置dept_id条件
- 然后用and()包裹一个Lambda表达式
- 在Lambda内部使用or()连接两个条件
生成的SQL是:WHERE dept_id = 10 AND (title = '高级' OR hire_years >= 3)
3.2 实现A OR (B AND C)结构
另一种常见场景是"满足A条件或者同时满足B和C条件"。比如查询"状态为待审核,或者(创建时间在今天且优先级高)"的工单:
QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.eq("status", "待审核") .or(qw -> qw.ge("create_time", LocalDate.now()) .eq("priority", "高"));这个例子中:
- 先用eq()设置status条件
- 然后用or()包裹Lambda表达式
- Lambda内部用ge()和eq()实现AND关系
生成的SQL是:WHERE status = '待审核' OR (create_time >= '2023-07-20' AND priority = '高')
3.3 多层嵌套的复杂场景
在一些特别复杂的查询中,可能需要三层甚至更多层的嵌套。比如查询"(部门为技术部且(职称为高级或工作满5年))或(部门为市场部且KPI达标)"的员工:
QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.and(qw1 -> qw1.eq("dept", "技术部") .and(qw2 -> qw2.eq("title", "高级") .or().ge("work_years", 5))) .or(qw -> qw.eq("dept", "市场部") .eq("kpi", "达标"));这种多层嵌套的写法虽然复杂,但只要遵循"每个括号对应一个Lambda表达式"的原则,就能清晰地构建出想要的查询结构。
4. 常见问题与解决方案
4.1 括号位置错误的排查
刚开始使用嵌套查询时,我最常遇到的问题就是生成的SQL括号位置不对。比如想要A AND (B OR C),结果生成了(A AND B) OR C。这种情况通常是因为Lambda表达式的作用范围没搞清楚。
错误示例:
// 错误的写法:生成的SQL是 (dept_id = 10 AND title = '高级') OR hire_years >= 3 wrapper.eq("dept_id", 10) .eq("title", "高级") .or().ge("hire_years", 3);正确写法:
// 正确的写法:生成的SQL是 dept_id = 10 AND (title = '高级' OR hire_years >= 3) wrapper.eq("dept_id", 10) .and(qw -> qw.eq("title", "高级").or().ge("hire_years", 3));4.2 条件优先级混淆
另一个常见问题是逻辑运算符的优先级混淆。SQL中AND的优先级高于OR,所以A AND B OR C实际上等同于(A AND B) OR C。如果不加括号明确优先级,可能会得到不符合预期的查询结果。
解决方案:
- 明确使用括号来指定优先级
- 复杂的条件组合尽量拆分成多个Lambda表达式
- 使用QueryWrapper的nested()方法进行更精细的控制
4.3 动态条件构建技巧
在实际项目中,查询条件往往是动态的。比如用户可能只填写了部分筛选条件。这时候就需要动态构建QueryWrapper:
QueryWrapper<User> wrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(name)) { wrapper.like("name", name); } if (minAge != null) { wrapper.ge("age", minAge); } if (maxAge != null) { wrapper.le("age", maxAge); } if (hobbies != null && !hobbies.isEmpty()) { wrapper.and(qw -> { for (String hobby : hobbies) { qw.or().like("hobbies", hobby); } }); }这种写法可以灵活应对各种条件组合,而且生成的SQL都是正确嵌套的。
5. 性能优化建议
5.1 避免过度嵌套
虽然嵌套查询很强大,但过度嵌套会影响SQL性能。特别是在处理大数据表时,建议:
- 嵌套层级不要超过3层
- 复杂的查询考虑拆分成多个简单查询
- 必要时使用JOIN代替嵌套条件
5.2 索引友好型写法
为了让查询能够利用索引,在构建条件时要注意:
- 等值条件(=)放在OR条件的前面
- 范围查询(>,<)尽量放在最后
- 避免在索引列上使用NOT、!=等否定操作
例如:
// 好的写法:能够利用dept_id和status的索引 wrapper.eq("dept_id", 10) .and(qw -> qw.eq("status", 1).or().eq("status", 2)); // 不好的写法:无法利用索引 wrapper.eq("dept_id", 10) .and(qw -> qw.ne("status", 0).or().eq("status", 3));5.3 条件顺序优化
条件的顺序也会影响查询性能。一般原则是:
- 选择性高的条件放在前面
- 能够过滤掉大量数据的条件优先执行
- 计算成本高的条件尽量靠后
比如查询活跃用户:
// 优化后的写法:先过滤掉非活跃用户 wrapper.eq("is_active", 1) .and(qw -> qw.ge("last_login", "2023-01-01") .or().gt("login_count", 100));6. 实际项目中的应用案例
6.1 电商商品筛选系统
在一个电商后台,我们需要实现这样的商品筛选:
- 分类为电子产品
- 价格在1000-5000之间
- 并且(是新品或评分大于4.5)
用QueryWrapper实现如下:
QueryWrapper<Product> wrapper = new QueryWrapper<>(); wrapper.eq("category", "电子产品") .between("price", 1000, 5000) .and(qw -> qw.eq("is_new", true).or().gt("rating", 4.5));这个查询用到了and()和or()的嵌套组合,生成的SQL既清晰又高效。
6.2 人力资源管理系统
在员工查询功能中,需要支持这样的条件:
- 部门为研发部或测试部
- 并且(职称为高级工程师或工作年限大于3年)
实现代码:
QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.and(qw -> qw.eq("dept", "研发部").or().eq("dept", "测试部")) .and(qw -> qw.eq("title", "高级工程师").or().gt("work_years", 3));这种写法既保持了代码的可读性,又准确表达了复杂的业务逻辑。
6.3 内容管理系统
在文章管理后台,常见的查询需求如:
- 状态为已发布
- 并且(创建时间在最近7天或阅读量大于1000)
- 并且(标签包含Java或标签包含Spring)
QueryWrapper实现:
QueryWrapper<Article> wrapper = new QueryWrapper<>(); wrapper.eq("status", "已发布") .and(qw -> qw.ge("create_time", LocalDate.now().minusDays(7)) .or().gt("view_count", 1000)) .and(qw -> { qw.like("tags", "Java").or().like("tags", "Spring"); });这个例子展示了多层次的嵌套组合,每个括号都对应着业务逻辑中的一个明确分组。
