You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA Criteria API实现带过滤条件的一对多join查询

JPA Criteria API 实现一对多关联条件过滤查询方案

实体类定义

首先按照表结构定义两个JPA实体,配置一对多关联关系:

  • Enterprise实体对应企业表,和Product是一对多关系
  • Product实体对应产品表,用枚举标识产品状态
// 产品状态枚举
public enum ProductStatus {
    ACTIVE, DISABLE
}

@Entity
@Table(name = "enterprise")
public class Enterprise {
    @Id
    private Long id;
    private String name;
    // 关联配置为懒加载,不做全局过滤,查询时动态控制关联条件
    @OneToMany(mappedBy = "enterprise", fetch = FetchType.LAZY)
    private List<Product> products;

    // 省略getter、setter
}

@Entity
@Table(name = "product")
public class Product {
    @Id
    private Long id;
    private String name;
    @Enumerated(EnumType.STRING)
    private ProductStatus status;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "enterprise_id")
    private Enterprise enterprise;

    // 省略getter、setter
}

核心查询逻辑实现

使用Criteria API编写查询时,关键是用fetch join加载关联数据,同时把产品状态的过滤条件写在关联的on子句上,而不是全局where子句上,避免把无有效产品的企业整条过滤掉。

@Repository
public class EnterpriseRepository {
    @PersistenceContext
    private EntityManager entityManager;

    public List<Enterprise> queryEnterprisesWithActiveProducts() {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Enterprise> cq = cb.createQuery(Enterprise.class);
        Root<Enterprise> enterpriseRoot = cq.from(Enterprise.class);

        // 左连接fetch抓取产品集合,保证没有有效产品的企业也能被查询到
        Join<Enterprise, Product> productJoin = (Join<Enterprise, Product>) enterpriseRoot
                .fetch("products", JoinType.LEFT);
        // 仅在关联时加载状态为ACTIVE的产品,条件写在on上而非where
        productJoin.on(cb.equal(productJoin.get("status"), ProductStatus.ACTIVE));

        // 加distinct去重,避免一对多关联查询产生重复的企业实体
        cq.select(enterpriseRoot).distinct(true);

        return entityManager.createQuery(cq).getResultList();
    }
}

注意:如果项目开启了JPA元模型生成,也可以用元模型属性(Enterprise_.products、Product_.status)替代字符串属性名,实现类型安全的属性访问,效果一致。

结果序列化处理

查询得到的Enterprise实体中,products集合已经只包含状态为ACTIVE的产品,直接做JSON序列化即可得到要求的结构。如果需要严格控制返回字段,也可以转换为DTO输出:

// 返回结构DTO
public record EnterpriseVO(Long id, String name, List<ProductVO> products) {}
public record ProductVO(Long id, String name) {}

// 结果转换
List<Enterprise> enterpriseList = enterpriseRepository.queryEnterprisesWithActiveProducts();
List<EnterpriseVO> result = enterpriseList.stream()
        .map(ent -> new EnterpriseVO(
                ent.getId(),
                ent.getName(),
                ent.getProducts().stream()
                        .map(prod -> new ProductVO(prod.getId(), prod.getName()))
                        .toList()
        )).toList();

针对题目给出的示例数据,最终输出的JSON结构和要求完全一致:ID=1的企业Alex关联的产品集合仅包含Iphone12、Iphone11两个ACTIVE状态的条目,DISABLE状态的Iphone13不会出现在结果中。

常见避坑说明

  • 不要在实体的@OneToMany关联上加@Where(clause = "status = 'ACTIVE'")做全局过滤:这种方式会导致所有查询企业的场景都自动过滤非激活产品,灵活性极差
  • 不要先查所有企业再循环遍历查询每个企业的激活产品:会产生N+1查询问题,数据量大时性能严重下降
  • 不要把产品状态过滤条件写在where子句中:会导致没有激活产品的企业被整条过滤,不符合业务预期
  • 不要遗漏distinct(true)配置:一对多fetch join会生成笛卡尔积结果,同一个企业会对应多行产品记录,不加去重会返回重复的企业对象

内容的提问来源于stack exchange,提问作者Son Nguyen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 21:12:19