MybatisPlus模糊查询进阶:用Lambda和函数式接口优雅处理多条件动态拼接
MyBatisPlus动态查询的艺术Lambda与函数式编程实战指南在Java持久层框架的演进历程中MyBatisPlus以其对MyBatis的优雅增强逐渐成为企业级应用开发的主流选择。特别是在处理复杂查询场景时传统的字符串拼接式SQL构建方式不仅容易出错也难以维护。本文将深入探讨如何利用Lambda表达式和函数式接口构建类型安全、可读性强的动态查询逻辑解决多条件模糊查询中的典型痛点。1. 从基础到进阶查询构建器的进化之路1.1 QueryWrapper的局限性传统QueryWrapper虽然简单易用但在处理动态条件时暴露出明显缺陷QueryWrapperUser wrapper new QueryWrapper(); if (StringUtils.isNotBlank(name)) { wrapper.like(name, name); } if (status ! null) { wrapper.eq(status, status); }这种写法存在三个主要问题字段名以字符串形式存在重构时容易出错条件组合逻辑与业务代码高度耦合复杂条件嵌套时代码可读性急剧下降1.2 LambdaQueryWrapper的优势LambdaQueryWrapper通过方法引用解决了类型安全问题LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.like(User::getName, name) .eq(User::getStatus, status);关键改进点编译期类型检查字段引用在编译时验证IDE智能提示支持代码自动补全重构友好字段名修改自动同步提示对于简单查询LambdaQueryWrapper已经足够。但当条件组合变得复杂时我们需要更高级的模式。2. 动态条件构建的优雅实践2.1 谓词收集器模式面对多条件动态拼接场景推荐将条件收集与条件应用分离ListPredicateUser predicates new ArrayList(); if (StringUtils.isNotBlank(keyword)) { predicates.add(u - u.getName().contains(keyword)); predicates.add(u - u.getEmail().contains(keyword)); } if (departmentId ! null) { predicates.add(u - u.getDepartmentId().equals(departmentId)); } LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); predicates.forEach(wrapper::or);这种模式的优势在于条件收集阶段专注于业务规则条件应用阶段统一处理SQL拼接易于扩展新的查询维度2.2 条件组合策略实际业务中常见的组合策略对比策略类型适用场景示例代码AND连接必须满足所有条件wrapper.and(predicate)OR连接满足任一条件即可wrapper.or(predicate)嵌套组合复杂逻辑表达式wrapper.and(w - w.or(...))对于模糊查询特别密集的场景可以构建专用工具类public class QueryUtils { public static T void addFuzzyConditions( LambdaQueryWrapperT wrapper, String keyword, FunctionT, ?... getters) { if (StringUtils.isBlank(keyword)) return; wrapper.and(w - { for (FunctionT, ? getter : getters) { w.or().like(getter, keyword); } }); } }3. 性能优化与陷阱规避3.1 索引命中策略模糊查询的索引使用是个常见痛点以下优化方案值得考虑右模糊优先LIKE prefix%可以利用索引全文索引对于频繁的全文搜索场景搜索引擎集成Elasticsearch等专业方案// 不推荐 - 无法使用索引 wrapper.like(User::getName, % keyword %); // 推荐 - 可能使用索引 wrapper.likeRight(User::getName, keyword);3.2 条件短路优化当某些条件组合可能导致性能问题时应提前判断public LambdaQueryWrapperUser buildQuery(SearchCriteria criteria) { LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); if (criteria.hasKeyword()) { // 关键词搜索限制结果集大小 wrapper.last(LIMIT 1000); } // 其他条件... return wrapper; }4. 复杂业务场景实战4.1 多字段动态搜索电商商品搜索的典型实现public PageProduct searchProducts(ProductQuery query, Pageable pageable) { LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); // 基础条件 wrapper.eq(Product::getDeleted, false); // 动态条件构建 Optional.ofNullable(query.getCategoryId()) .ifPresent(id - wrapper.eq(Product::getCategoryId, id)); if (StringUtils.isNotBlank(query.getKeyword())) { String keyword query.getKeyword().trim(); wrapper.and(w - w .like(Product::getName, keyword) .or() .like(Product::getDescription, keyword) .or() .like(Product::getSkuCode, keyword)); } // 价格区间 if (query.getMinPrice() ! null query.getMaxPrice() ! null) { wrapper.between(Product::getPrice, query.getMinPrice(), query.getMaxPrice()); } return productMapper.selectPage(new Page(pageable.getPageNumber(), pageable.getPageSize()), wrapper); }4.2 条件分组处理对于权限过滤等需要分组处理的场景LambdaQueryWrapperOrder wrapper new LambdaQueryWrapper(); // 业务条件 wrapper.eq(Order::getStatus, status); // 权限条件 wrapper.and(w - { if (currentUser.isAdmin()) { return; // 无限制 } else if (currentUser.isDepartmentManager()) { w.eq(Order::getDepartmentId, currentUser.getDepartmentId()); } else { w.eq(Order::getUserId, currentUser.getId()); } });5. 架构层面的思考5.1 查询对象模式对于特别复杂的查询场景建议引入专用查询对象public class UserQuery { private String keyword; private ListLong departmentIds; private UserStatus status; // 其他查询条件... public LambdaQueryWrapperUser toWrapper() { LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); if (StringUtils.isNotBlank(keyword)) { wrapper.and(w - w .like(User::getName, keyword) .or() .like(User::getEmail, keyword)); } if (!CollectionUtils.isEmpty(departmentIds)) { wrapper.in(User::getDepartmentId, departmentIds); } if (status ! null) { wrapper.eq(User::getStatus, status); } return wrapper; } }5.2 与Spring Data的协同在Spring生态中可以结合Specification实现更灵活的查询public class UserSpecs { public static SpecificationUser hasKeyword(String keyword) { return (root, query, cb) - cb.or( cb.like(root.get(name), % keyword %), cb.like(root.get(email), % keyword %) ); } } // 使用示例 userRepository.findAll( Specifications.where(UserSpecs.hasKeyword(keyword)) .and(UserSpecs.inDepartments(departments)), pageable );在实际项目中根据查询复杂度选择MyBatisPlus的Wrapper或JPA Specification两者各有优劣。Wrapper更适合MyBatis生态而Specification则与Spring Data集成更紧密。