当前位置: 首页 > news >正文

MyBatis-Plus动态查询实战:用QueryWrapper的and()和or()优雅构建商品筛选与权限查询

MyBatis-Plus动态查询实战:用QueryWrapper的and()和or()优雅构建商品筛选与权限查询

在电商后台和权限管理系统中,动态查询是最常见的需求之一。想象一下这样的场景:用户在前端勾选了多个筛选条件,后台需要将这些条件灵活组合成SQL查询;或者不同角色的用户登录后,系统需要根据其权限范围动态拼接数据过滤条件。传统的手写SQL或简单拼接字符串的方式不仅难以维护,还容易引入安全风险。这正是MyBatis-Plus的QueryWrapper大显身手的地方。

QueryWrapper提供的and()or()方法,配合lambda表达式,能够以类型安全、可读性强的方式构建复杂的动态查询条件。本文将深入两个典型业务场景——电商商品多维度筛选动态数据权限控制,展示如何将这些业务需求优雅地转化为QueryWrapper的链式调用。

1. 电商商品多维度筛选实战

电商平台的商品筛选往往包含数十个可组合的条件,比如价格区间、商品分类、库存状态、促销标签等。这些条件之间可能存在"且"和"或"的复杂关系。

1.1 基础筛选条件构建

假设我们需要实现以下筛选逻辑:

  • 商品状态必须为上架(status=1)
  • 属于手机或电脑分类(category_id IN (1,2))
  • 价格在1000-5000元之间(price BETWEEN 1000 AND 5000)
  • 同时参与"618促销"或"双11促销"(promotion_type IN (3,4))

对应的QueryWrapper实现:

QueryWrapper<Product> 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 QueryWrapper<Product> buildProductQuery(ProductQueryDTO queryDTO) { QueryWrapper<Product> 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", "游戏本"));

对应的SQL:

WHERE (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(QueryWrapper<T> 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 List<Order> queryOrderList(OrderQuery query) { QueryWrapper<Order> 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 QueryWrapper<Product> activeProducts() { return new QueryWrapper<Product>() .eq("status", 1) .gt("stock", 0); } } // 使用示例 QueryWrapper<Product> wrapper = QueryWrapperFactory.activeProducts() .like("name", keyword);

3.3 性能优化建议

  1. 索引友好:确保QueryWrapper构建的条件能够利用数据库索引
  2. 避免过度嵌套:太深的嵌套条件可能导致SQL解析性能下降
  3. 参数化查询:始终使用预编译参数而非字符串拼接
  4. 批量操作:对于大批量数据,考虑使用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 查询逻辑分层

良好的架构应该将查询逻辑分层管理:

  1. Controller层:接收参数,调用Service
  2. Service层:组合业务逻辑和查询条件
  3. QueryBuilder层:专门负责构建复杂查询条件
// 在Service中的典型实现 public PageResult<Product> queryProducts(ProductQuery query, PageParam page) { QueryWrapper<Product> wrapper = new ProductQueryWrapperBuilder() .withKeyword(query.getKeyword()) .withCategory(query.getCategoryId()) .withPriceRange(query.getMinPrice(), query.getMaxPrice()) .build(); Page<Product> pageResult = productMapper.selectPage( new Page<>(page.getPageNum(), page.getPageSize()), wrapper); return PageResult.of(pageResult); }

4.2 使用Specification模式

对于极其复杂的查询,可以考虑引入Specification模式:

public interface ProductSpecification { void apply(QueryWrapper<Product> wrapper); } public class PriceRangeSpec implements ProductSpecification { private final BigDecimal min; private final BigDecimal max; // 构造器省略 @Override public void apply(QueryWrapper<Product> wrapper) { wrapper.between("price", min, max); } } // 使用示例 List<ProductSpecification> specs = Arrays.asList( new PriceRangeSpec(minPrice, maxPrice), new CategorySpec(categoryId) ); QueryWrapper<Product> wrapper = new QueryWrapper<>(); specs.forEach(spec -> spec.apply(wrapper));

4.3 查询条件缓存

对于频繁使用的复杂查询条件,可以考虑缓存QueryWrapper实例:

public class QueryWrapperCache { private static final Map<String, QueryWrapper<Product>> CACHE = new ConcurrentHashMap<>(); public static QueryWrapper<Product> getFeaturedProductsWrapper() { return CACHE.computeIfAbsent("featured", key -> new QueryWrapper<Product>() .eq("is_featured", 1) .orderByDesc("featured_order")); } }
http://www.gsyq.cn/news/1519421.html

相关文章:

  • 高数期末救命!72道不定积分题里,这5类‘换元法’套路必须掌握(附解题模板)
  • 终端与IDE形态的vibe coding实测:两款AI编程工具迭代能力对比
  • 深度解析发酵饲料:核心原理、应用价值与养殖实践 - 速递信息
  • 2026靠前境内外EMBA客观测评:理性择校全指南 - 品牌2026推荐
  • 2026年6月在线浊度计知名品牌排行榜:国产力量崛起与技术格局重塑 - 液体流量液位品牌推荐
  • ParsecVDisplay虚拟显示器实战指南:3个高级技巧打造专业级多屏工作站
  • i.MX21 GPIO与PWM寄存器深度解析与嵌入式开发实战指南
  • 从审核员视角看漏洞:拆解CNVD收录标准,理解安全风险的‘轻重缓急’
  • 宜宾业之峰装饰官方联系方式 咨询电话 官方网站 官网 - 速递信息
  • Unsloth+AutoAWQ+SGLang:LLM轻量化落地三件套实战指南
  • 微信聊天记录备份工具:如何安全迁移你的重要对话数据
  • Cursor免费试用终极解决方案:三步快速重置机器码恢复AI编程助手功能
  • 2026年西安PMP培训1980元课程怎么咨询?试听课、35学时和报考指导入口,众智商学院官网400冯老师 - 众智商学院职业教育
  • DSGE模型终极指南:如何从零开始掌握宏观经济建模的40个经典案例
  • 3分钟搞定学术付费墙:Unpaywall浏览器扩展完整使用指南
  • Linux内核学习轨迹第七部: 多队列块层blk-mq深度拆解(第四节)
  • 英雄联盟玩家如何通过本地化工具提升80%游戏效率:League Akari全面解析
  • 别再被路由器宣传的‘千兆WiFi’忽悠了!手把手教你用公式算清802.11ax的真实速度
  • RAG 上下文组装:检索结果不是直接塞给大模型
  • 当AI编程助手突然罢工:Cursor试用限制的智能解决方案
  • 终极指南:如何用ZXing-C++库轻松实现多格式条码识别与生成
  • 3步解决Cursor试用限制:实用技巧分享
  • 5分钟搭建专业级富文本编辑器:wangEditor v5完整教程
  • 你的Google验证码为什么30秒变一次?一文拆解TOTP算法核心与时钟同步的那些坑
  • 3步搞定DevOps转型:OneDev如何让中小团队告别工具碎片化?
  • Blender建筑建模终极指南:building_tools完整使用教程
  • 别再只记结论了!通过5个PyTorch代码实验,亲手验证model.eval()与torch.no_grad()的真实影响
  • ARM9嵌入式开发实战:MC9328MXS I2C与SSI接口深度编程与调试指南
  • MC9S08SV16中断优先级与TPMV3定时器实战:提升嵌入式实时性与PWM精度
  • 如何快速实现通达信缠论分析:3分钟安装终极指南