MyBatis-Plus动态查询进阶:巧用Wrappers.lambdaQuery()实现复杂条件过滤(附避坑点)
在企业级应用开发中,数据查询往往面临动态条件组合、类型安全校验和性能优化等多重挑战。MyBatis-Plus的LambdaQueryWrapper通过Lambda表达式将SQL条件构建变得直观且安全,但真正发挥其威力需要掌握一些高阶技巧。本文将深入探讨如何用Wrappers.lambdaQuery()解决实际开发中的复杂查询场景。
1. 动态条件构建的艺术
动态查询的核心在于根据运行时参数灵活组装WHERE条件。传统字符串拼接方式容易引发SQL注入和类型错误,而LambdaQueryWrapper提供了更优雅的解决方案。
基础动态条件示例:
public List<User> searchUsers(UserQuery query) { LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(); if (StringUtils.isNotBlank(query.getName())) { wrapper.like(User::getName, query.getName()); } if (query.getMinAge() != null) { wrapper.ge(User::getAge, query.getMinAge()); } if (query.getDepartmentIds() != null && !query.getDepartmentIds().isEmpty()) { wrapper.in(User::getDepartmentId, query.getDepartmentIds()); } return userMapper.selectList(wrapper); }条件短路优化技巧:
wrapper.gt(User::getSalary, 10000) .and(q -> q.eq(User::getLevel, 3).or().eq(User::getLevel, 4)));2. 复杂业务场景实战
2.1 多表关联查询
虽然LambdaQueryWrapper主要针对单表操作,但通过子查询可以实现关联过滤:
wrapper.inSql(User::getDepartmentId, "SELECT id FROM department WHERE status = 1");2.2 动态权限过滤
实现数据行级权限控制的典型模式:
wrapper.apply("create_by = {0} OR visibility = {1}", currentUserId, Visibility.PUBLIC.ordinal());2.3 报表数据筛选
处理包含日期范围、多状态组合的统计查询:
wrapper.between(Order::getCreateTime, startDate, endDate) .in(Order::getStatus, Arrays.asList(Status.PAID, Status.SHIPPED)) .groupBy(Order::getProductId);3. 性能陷阱与避坑指南
3.1 NPE防护最佳实践
// 不安全的写法 wrapper.eq(User::getName, request.getName()); // 安全写法 Optional.ofNullable(request.getName()) .ifPresent(name -> wrapper.eq(User::getName, name));3.2 索引失效场景
- 避免在索引列上使用
NOT LIKE - 谨慎使用
OR条件组合 - 函数转换会导致索引失效:
// 错误的写法 wrapper.apply("DATE(create_time) = {0}", today); // 正确的写法 wrapper.between(Order::getCreateTime, todayStart, todayEnd);
3.3 条件顺序优化
MyBatis-Plus的条件组装顺序会影响最终SQL性能:
// 性能较差的顺序 wrapper.like(User::getName, "%张%") .eq(User::getStatus, 1); // 优化后的顺序 wrapper.eq(User::getStatus, 1) .like(User::getDescription, "%张%");4. 高级技巧与模式
4.1 条件复用组件
封装可复用的条件构建逻辑:
public class UserQueryBuilder { public static LambdaQueryWrapper<User> buildActiveUsersWrapper() { return Wrappers.lambdaQuery(User.class) .eq(User::getIsActive, true) .isNotNull(User::getEmail); } }4.2 动态排序实现
wrapper.orderBy(true, request.isAsc(), User::getCreateTime);4.3 条件构建器模式
对于极其复杂的查询场景,可以采用建造者模式:
UserQueryBuilder.builder() .nameContains("张") .ageBetween(20, 30) .departments("IT", "Finance") .build();5. 调试与问题排查
5.1 SQL日志分析
配置mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl查看完整SQL
5.2 条件验证技巧
System.out.println(wrapper.getCustomSqlSegment());5.3 常见异常处理
Invalid bound statement:检查Mapper接口与Wrapper泛型类型是否一致Parameter index out of bounds:检查条件参数是否全部正确传递
在实际项目中使用这些技巧时,我发现最容易被忽视的是条件顺序对性能的影响。曾经有个查询从5秒优化到200毫秒,仅仅是通过调整了条件的先后顺序。另一个常见误区是过度使用apply()直接编写SQL片段,这虽然灵活但丧失了类型安全的优势。