如何在Criteria Builder中结合Group By与Order By解决重复预订问题
解决方案:通过Criteria Builder实现Group By与Order By并解决重复Booking问题
你的问题核心是多表关联导致的笛卡尔积,即使使用distinct,由于你同时查询了Booking实体和不同的出发日期(DEP_DATE_STR),同一个Booking会因为关联多个TrainJourney而生成多条不同的结果行,最终表现为重复的Booking记录。以下是具体的解决步骤和代码示例:
核心思路
- 用
join替代fetch:fetch用于实体关联的懒加载初始化,而分组查询需要明确的关联路径来构建聚合逻辑。 - 按
bookingId分组:确保每个Booking只返回一行结果。 - 聚合出发日期:因为一个
Booking可能对应多个TrainJourney,需要通过聚合函数(如min/max)确定要展示的出发日期(比如最早的出发日期)。 - 结合聚合后的日期实现排序。
代码实现
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Object[]> query = cb.createQuery(Object[].class); Root<Booking> bookingRoot = query.from(Booking.class); // 替换fetch为join,构建明确的关联路径 Join<Booking, Product> productJoin = bookingRoot.join("products", JoinType.LEFT); Join<Product, TrainBooking> trainBookingJoin = productJoin.join("trainBooking", JoinType.LEFT); productJoin.join("provider", JoinType.LEFT); Join<TrainBooking, TrainJourney> trainJourneyJoin = trainBookingJoin.join("trainJourneys", JoinType.LEFT); Join<TrainJourney, Journey> journeyJoin = trainJourneyJoin.join("journey", JoinType.LEFT); // 聚合出发日期:这里以取最早的出发日期为例,可根据业务需求替换为max Expression<Date> aggregatedDepDate = cb.min(journeyJoin.get(DEP_DATE_STR)); // 按bookingId分组,确保每个Booking只返回一行 query.groupBy(bookingRoot.get("bookingId")); // 选择Booking实体和聚合后的出发日期,同时配置排序 query.multiselect(bookingRoot, aggregatedDepDate) // 先按出发日期排序,再结合原有的排序规则 .orderBy(cb.asc(aggregatedDepDate), getOrderParam(request, cb, bookingRoot)) .where(createPredicates(request, otaUser, cb, bookingRoot));
关键说明
- 为什么
distinct失效?:你之前的查询返回的是Booking+出发日期的组合,同一个Booking对应多个不同的出发日期时,这些组合都是唯一的,distinct无法对Booking单独去重。 - 聚合函数的选择:如果业务需要展示最晚的出发日期,将
cb.min替换为cb.max即可;如果需要其他逻辑,可根据JPA支持的聚合函数调整。 - 排序逻辑:示例中先按聚合后的出发日期排序,再保留你原有的
orderParam排序规则,可根据业务需求调整排序顺序。
内容的提问来源于stack exchange,提问作者Fatih Enes
相关产品推荐
相关产品推荐

