MyBatis-Plus动态查询实战:用QueryWrapper的and()和or()优雅构建商品筛选与权限查询
MyBatis-Plus动态查询实战用QueryWrapper的and()和or()优雅构建商品筛选与权限查询在电商后台和权限管理系统中动态查询是最常见的需求之一。想象一下这样的场景用户在前端勾选了多个筛选条件后台需要将这些条件灵活组合成SQL查询或者不同角色的用户登录后系统需要根据其权限范围动态拼接数据过滤条件。传统的手写SQL或简单拼接字符串的方式不仅难以维护还容易引入安全风险。这正是MyBatis-Plus的QueryWrapper大显身手的地方。QueryWrapper提供的and()和or()方法配合lambda表达式能够以类型安全、可读性强的方式构建复杂的动态查询条件。本文将深入两个典型业务场景——电商商品多维度筛选和动态数据权限控制展示如何将这些业务需求优雅地转化为QueryWrapper的链式调用。1. 电商商品多维度筛选实战电商平台的商品筛选往往包含数十个可组合的条件比如价格区间、商品分类、库存状态、促销标签等。这些条件之间可能存在且和或的复杂关系。1.1 基础筛选条件构建假设我们需要实现以下筛选逻辑商品状态必须为上架status1属于手机或电脑分类category_id IN (1,2)价格在1000-5000元之间price BETWEEN 1000 AND 5000同时参与618促销或双11促销promotion_type IN (3,4)对应的QueryWrapper实现QueryWrapperProduct wrapper new QueryWrapper(); wrapper.eq(status, 1) .in(category_id, Arrays.asList(1, 2)) .between(price, 1000, 5000) .and(qw - qw.in(promotion_type, 3).or().in(promotion_type, 4));生成的SQL将是WHERE status 1 AND category_id IN (1, 2) AND price BETWEEN 1000 AND 5000 AND (promotion_type IN (3) OR promotion_type IN (4))1.2 处理前端动态参数实际场景中筛选条件往往是动态的。我们可以封装一个构建方法public QueryWrapperProduct buildProductQuery(ProductQueryDTO queryDTO) { QueryWrapperProduct wrapper new QueryWrapper(); wrapper.eq(queryDTO.getStatus() ! null, status, queryDTO.getStatus()); if (CollectionUtils.isNotEmpty(queryDTO.getCategoryIds())) { wrapper.in(category_id, queryDTO.getCategoryIds()); } wrapper.between(queryDTO.getMinPrice() ! null queryDTO.getMaxPrice() ! null, price, queryDTO.getMinPrice(), queryDTO.getMaxPrice()); if (CollectionUtils.isNotEmpty(queryDTO.getPromotionTypes())) { wrapper.and(qw - { for (Integer type : queryDTO.getPromotionTypes()) { qw.or().eq(promotion_type, type); } }); } return wrapper; }提示eq(boolean condition, String column, Object val)这种条件方法可以在condition为false时跳过该条件非常适合处理动态参数。1.3 复杂嵌套条件示例更复杂的场景可能需要嵌套条件组合。例如筛选手机分类下价格低于3000的苹果手机或者电脑分类下价格高于5000的游戏本wrapper.and(qw - qw.eq(category_id, 1) .lt(price, 3000) .eq(brand_id, 10)) .or(qw - qw.eq(category_id, 2) .gt(price, 5000) .like(name, 游戏本));对应的SQLWHERE (category_id 1 AND price 3000 AND brand_id 10) OR (category_id 2 AND price 5000 AND name LIKE %游戏本%)2. 动态数据权限查询实现数据权限是企业管理系统的核心需求不同角色、不同部门的用户应该只能看到自己有权限访问的数据。这种权限通常需要动态应用到所有查询中。2.1 基于角色的数据权限控制假设系统有以下权限规则管理员查看所有数据部门经理查看本部门及下属部门数据普通员工仅查看自己创建的数据我们可以创建一个权限查询构建器public class DataPermissionHelper { public static T void addDataPermission(QueryWrapperT wrapper) { User currentUser SecurityUtils.getCurrentUser(); if (currentUser.isAdmin()) { return; // 管理员无限制 } if (currentUser.isDeptManager()) { wrapper.and(qw - qw.inSql(dept_id, SELECT id FROM department WHERE parent_ids LIKE currentUser.getDeptId() ,% OR id currentUser.getDeptId())); } else { wrapper.eq(create_by, currentUser.getUserId()); } } }在Service层使用public ListOrder queryOrderList(OrderQuery query) { QueryWrapperOrder wrapper new QueryWrapper(); // 添加业务查询条件 wrapper.eq(StringUtils.isNotBlank(query.getOrderNo()), order_no, query.getOrderNo()); // 添加数据权限条件 DataPermissionHelper.addDataPermission(wrapper); return orderMapper.selectList(wrapper); }2.2 多维度权限组合更复杂的权限系统可能需要组合多种条件。例如销售人员可以查看自己负责客户的订单以及所在团队共享的订单wrapper.and(qw - qw.eq(salesman_id, currentUser.getUserId()) .or() .inSql(order_id, SELECT order_id FROM team_share WHERE team_id currentUser.getTeamId()));2.3 权限条件与业务条件的安全组合当权限条件需要与业务条件组合时要特别注意条件的嵌套关系。错误的组合可能导致权限失效// 危险写法权限条件可能被绕过 wrapper.eq(type, query.getType()) .or() .eq(create_by, currentUser.getUserId()); // 安全写法确保权限条件始终生效 wrapper.and(qw - qw.eq(type, query.getType()) .or() .eq(create_by, currentUser.getUserId()));3. QueryWrapper的高级用法与最佳实践3.1 条件优先级控制理解条件组合的优先级对于构建正确的查询至关重要。MyBatis-Plus的条件组合遵循以下规则组合方式SQL示例说明.eq().eq()a 1 AND b 2默认使用AND连接.eq().or().eq()a 1 OR b 2使用OR连接前后条件.eq().or(qw - qw.eq().eq())a 1 OR (b 2 AND c 3)使用lambda创建嵌套条件.and(qw - qw.eq().or().eq())(a 1 OR b 2)AND连接嵌套条件3.2 复用查询条件对于常用的条件组合可以创建可重用的条件构建器public class QueryWrapperFactory { public static QueryWrapperProduct activeProducts() { return new QueryWrapperProduct() .eq(status, 1) .gt(stock, 0); } } // 使用示例 QueryWrapperProduct wrapper QueryWrapperFactory.activeProducts() .like(name, keyword);3.3 性能优化建议索引友好确保QueryWrapper构建的条件能够利用数据库索引避免过度嵌套太深的嵌套条件可能导致SQL解析性能下降参数化查询始终使用预编译参数而非字符串拼接批量操作对于大批量数据考虑使用in()替代多个or()条件// 不推荐多个or条件 wrapper.eq(category, 1).or().eq(category, 2).or().eq(category, 3); // 推荐使用in查询 wrapper.in(category, Arrays.asList(1, 2, 3));4. 在Service层组织查询逻辑4.1 查询逻辑分层良好的架构应该将查询逻辑分层管理Controller层接收参数调用ServiceService层组合业务逻辑和查询条件QueryBuilder层专门负责构建复杂查询条件// 在Service中的典型实现 public PageResultProduct queryProducts(ProductQuery query, PageParam page) { QueryWrapperProduct wrapper new ProductQueryWrapperBuilder() .withKeyword(query.getKeyword()) .withCategory(query.getCategoryId()) .withPriceRange(query.getMinPrice(), query.getMaxPrice()) .build(); PageProduct pageResult productMapper.selectPage( new Page(page.getPageNum(), page.getPageSize()), wrapper); return PageResult.of(pageResult); }4.2 使用Specification模式对于极其复杂的查询可以考虑引入Specification模式public interface ProductSpecification { void apply(QueryWrapperProduct wrapper); } public class PriceRangeSpec implements ProductSpecification { private final BigDecimal min; private final BigDecimal max; // 构造器省略 Override public void apply(QueryWrapperProduct wrapper) { wrapper.between(price, min, max); } } // 使用示例 ListProductSpecification specs Arrays.asList( new PriceRangeSpec(minPrice, maxPrice), new CategorySpec(categoryId) ); QueryWrapperProduct wrapper new QueryWrapper(); specs.forEach(spec - spec.apply(wrapper));4.3 查询条件缓存对于频繁使用的复杂查询条件可以考虑缓存QueryWrapper实例public class QueryWrapperCache { private static final MapString, QueryWrapperProduct CACHE new ConcurrentHashMap(); public static QueryWrapperProduct getFeaturedProductsWrapper() { return CACHE.computeIfAbsent(featured, key - new QueryWrapperProduct() .eq(is_featured, 1) .orderByDesc(featured_order)); } }