按Id排序分页时不同页码出现同一实体的问题排查
分页查询重复实体问题的解决方案
问题描述
执行分页查询时,不同current_page返回结果中出现同一个CardsKanBanEntidade实体,数据库中ID无重复且不为null,切换为按ID排序后问题仍存在。
问题原因
核心原因是关联查询产生笛卡尔积:在Specification中使用root.join("kanbanLane")进行关联查询时,如果LanesKanBanEntidade与CardsKanBanEntidade的关联关系触发重复匹配(比如单条Card对应多条关联Lane记录),会生成重复的Card实体行。JPA分页是基于原始查询结果的行数进行分页,而非去重后的实体数,因此重复行被分到不同页面,导致同一实体出现在多页中。
解决方案
方案1:添加Distinct去重
在查询中强制开启去重,确保返回的实体唯一。修改过滤逻辑,在Specification中设置query.distinct(true):
Specification<CardsKanBanEntidade> filter = Specification.where((root, query, cb) -> { // 开启去重,消除重复实体行 query.distinct(true); return cb.equal(root.join("kanbanLane").get("passoId"), lane_id); }); Specification<CardsKanBanEntidade> lojaFilter = Specification.where(null); if (lojas != null && !lojas.isEmpty()) { lojaFilter = lojaFilter.and((root, query, cb) -> root.get("loja").in(lojas)); } filter = filter.and(lojaFilter);
方案2:用Exists子查询替代Join(更高效)
避免使用Join产生笛卡尔积,改用Exists子查询实现关联过滤,从根源上消除重复行:
Specification<CardsKanBanEntidade> filter = Specification.where((root, query, cb) -> { Subquery<LanesKanBanEntidade> subquery = query.subquery(LanesKanBanEntidade.class); Root<LanesKanBanEntidade> laneRoot = subquery.from(LanesKanBanEntidade.class); // 子查询匹配目标lane,并关联当前Card subquery.select(laneRoot) .where(cb.equal(laneRoot.get("passoId"), lane_id), cb.equal(root.get("kanbanLane"), laneRoot)); return cb.exists(subquery); }); Specification<CardsKanBanEntidade> lojaFilter = Specification.where(null); if (lojas != null && !lojas.isEmpty()) { lojaFilter = lojaFilter.and((root, query, cb) -> root.get("loja").in(lojas)); } filter = filter.and(lojaFilter);
验证建议
- 开启SQL日志,查看生成的SQL语句是否存在重复行
- 确认
LanesKanBanEntidade与CardsKanBanEntidade的关联关系是否符合业务逻辑(@ManyToOne应确保单Card对应单Lane,若实际是一对多则需检查数据或业务规则)
内容的提问来源于stack exchange,提问作者Davi Américo
相关产品推荐
相关产品推荐

