如何使用JPA在单查询中实现父子表多条件聚合统计?
用JPA实现单查询多聚合统计需求
我来帮你搞定这个JPA多聚合统计的需求!其实不用像原生SQL那样写子查询关联,JPA里有更简洁的实现方式,当然也能适配你原本的子查询思路,下面给你几种可行的方案:
方案一:JPQL条件聚合(推荐,更简洁高效)
首先假设你的实体类关联关系已经正确映射:
@Entity @Table(name = "Parents") public class Parent { @Id private Integer id; private String name; @OneToMany(mappedBy = "parent") private List<Item> items; // 构造器、getter、setter省略 } @Entity @Table(name = "Items") public class Item { @Id private Integer id; @ManyToOne @JoinColumn(name = "parent_id") private Parent parent; private Integer fieldA; private Integer fieldB; // 构造器、getter、setter省略 }
我们可以利用CASE WHEN配合聚合函数,在单查询里直接算出两个统计值,不用子查询:
public interface ParentRepository extends JpaRepository<Parent, Integer> { // 返回Object[]的版本 @Query("SELECT p.id, p.name, " + "COUNT(CASE WHEN i.fieldA = 1 THEN 1 END) AS count1, " + "COUNT(CASE WHEN i.fieldA = 1 AND i.fieldB = 2 THEN 1 END) AS count2 " + "FROM Parent p " + "INNER JOIN p.items i " + "GROUP BY p.id, p.name") List<Object[]> getParentAggregateStats(); }
- 这里用
INNER JOIN和你原SQL逻辑一致,只会返回有符合条件Item的Parent;如果要包含所有Parent(哪怕没有对应Item,统计数为0),换成LEFT JOIN即可。 COUNT会自动忽略NULL值,所以满足条件的记录会被计数,不满足的则不会。
用DTO接收结果(更优雅)
如果不想处理Object[],可以定义一个DTO类:
public class ParentStatsDTO { private Integer parentId; private String parentName; private Long count1; private Long count2; // 注意构造器参数顺序要和查询字段完全匹配 public ParentStatsDTO(Integer parentId, String parentName, Long count1, Long count2) { this.parentId = parentId; this.parentName = parentName; this.count1 = count1; this.count2 = count2; } // getter方法省略 }
然后修改Repository的查询语句:
@Query("SELECT new com.yourpackage.ParentStatsDTO(p.id, p.name, " + "COUNT(CASE WHEN i.fieldA = 1 THEN 1 END), " + "COUNT(CASE WHEN i.fieldA = 1 AND i.fieldB = 2 THEN 1 END)) " + "FROM Parent p " + "INNER JOIN p.items i " + "GROUP BY p.id, p.name") List<ParentStatsDTO> getParentAggregateStats();
方案二:适配你原有的子查询思路
如果你想严格遵循原生SQL的子查询关联逻辑,JPQL也支持子查询写法:
@Query("SELECT p.id, p.name, s1.count1, s2.count2 " + "FROM Parent p " + "INNER JOIN (SELECT COUNT(i.id) AS count1, i.parent.id AS parentId " + "FROM Item i " + "WHERE i.fieldA = 1 " + "GROUP BY i.parent.id) s1 ON s1.parentId = p.id " + "INNER JOIN (SELECT COUNT(i.id) AS count2, i.parent.id AS parentId " + "FROM Item i " + "WHERE i.fieldA = 1 AND i.fieldB = 2 " + "GROUP BY i.parent.id) s2 ON s2.parentId = p.id") List<Object[]> getParentAggregateStatsWithSubquery();
同样,你也可以用上面的ParentStatsDTO来接收结构化的结果。
方案三:Criteria API实现(适合动态查询场景)
如果后续需要动态调整统计条件,可以用JPA的Criteria API来构建查询:
@Service public class ParentStatsService { @Autowired private EntityManager entityManager; public List<ParentStatsDTO> getParentStatsWithCriteria() { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<ParentStatsDTO> query = cb.createQuery(ParentStatsDTO.class); Root<Parent> p = query.from(Parent.class); Join<Parent, Item> i = p.join("items", JoinType.INNER); // 构建count1的条件统计 Expression<Long> count1 = cb.count(cb.selectCase() .when(cb.equal(i.get("fieldA"), 1), 1) .otherwise(null)); // 构建count2的条件统计 Expression<Long> count2 = cb.count(cb.selectCase() .when(cb.and(cb.equal(i.get("fieldA"), 1), cb.equal(i.get("fieldB"), 2)), 1) .otherwise(null)); query.multiselect( p.get("id"), p.get("name"), count1, count2 ).groupBy(p.get("id"), p.get("name")); return entityManager.createQuery(query).getResultList(); } }
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

