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

Spring Data JPA能否创建递归查询遍历商品的所有分类父级?

递归查询实现分类父级链的商品选项遍历

当然可以用递归查询来实现这个需求!你当前的Java循环逻辑虽然能完成功能,但会带来N+1查询问题(加载所有分类后逐个遍历父级链,产生大量数据库调用),用递归查询把逻辑放到数据库层面执行,效率会高很多,代码也更简洁。

首先先整理你的实体类代码,方便参考:

@Entity 
public class Product { 
    @Id 
    @Column(unique = true) 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Integer id; 
    private String title; 
    @OneToOne 
    private Brand brand; 
    @OneToOne(fetch = FetchType.EAGER) 
    @OnDelete(action = OnDeleteAction.CASCADE) 
    private Category category; 
}

@Entity 
public class Category { 
    @Id 
    @Column(unique = true) 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Integer id; 
    @NotBlank(message = "Please enter category name!") 
    @Length(max = 50, message = "Maximum size exceeded!") 
    private String name; 
    @OneToOne 
    private Category parent; 
}

解决方案:递归CTE查询(推荐)

递归CTE(Common Table Expression)是大多数现代数据库(MySQL 8+、PostgreSQL、SQL Server等)支持的特性,可以一次性生成目标分类及其所有子分类的ID链,再关联查询对应的ProductOption。

方式1:原生SQL递归查询

在你的CategoryRepository中添加自定义查询方法:

public interface CategoryRepository extends JpaRepository<Category, Integer> {
    @Query(value = "WITH RECURSIVE category_hierarchy AS (" +
            "    SELECT id, parent_id FROM category WHERE id = :targetCategoryId " +
            "    UNION ALL " +
            "    SELECT c.id, c.parent_id FROM category c " +
            "    JOIN category_hierarchy ch ON c.parent_id = ch.id" +
            ") " +
            "SELECT po.* FROM product_option po " +
            "JOIN product p ON po.product_id = p.id " +
            "WHERE p.category_id IN (SELECT id FROM category_hierarchy)", 
            nativeQuery = true)
    List<ProductOption> findAllProductOptionsByCategoryHierarchy(Integer targetCategoryId);
}

方式2:Hibernate 6+的HQL递归查询

如果使用Hibernate 6及以上版本,可以用更贴近JPA实体的HQL语法:

@Query("WITH RECURSIVE category_hierarchy(cat) AS (" +
        "    SELECT c FROM Category c WHERE c.id = :targetCategoryId " +
        "    UNION ALL " +
        "    SELECT c FROM Category c JOIN category_hierarchy ch ON c.parent = ch.cat" +
        ") " +
        "SELECT po FROM ProductOption po " +
        "JOIN po.product p " +
        "WHERE p.category IN (SELECT ch.cat FROM category_hierarchy ch)")
List<ProductOption> findAllProductOptionsByCategoryHierarchyHQL(Integer targetCategoryId);

根据分类名称查询(比如"For Woman")

如果需要直接通过分类名称匹配,修改查询条件即可:

@Query(value = "WITH RECURSIVE category_hierarchy AS (" +
        "    SELECT id, parent_id FROM category WHERE name = :categoryName " +
        "    UNION ALL " +
        "    SELECT c.id, c.parent_id FROM category c " +
        "    JOIN category_hierarchy ch ON c.parent_id = ch.id" +
        ") " +
        "SELECT po.* FROM product_option po " +
        "JOIN product p ON po.product_id = p.id " +
        "WHERE p.category_id IN (SELECT id FROM category_hierarchy)", 
        nativeQuery = true)
List<ProductOption> findAllProductOptionsByCategoryNameHierarchy(String categoryName);

替换原有Java逻辑

用上面的查询方法替代你原来的循环代码,只需要一行就能得到结果:

// 示例:根据分类ID查询
Integer targetCategoryId = ...; // 比如"For Woman"分类的ID
List<ProductOption> productOptions = categoryRepository.findAllProductOptionsByCategoryHierarchy(targetCategoryId);

// 或者根据分类名称查询
List<ProductOption> productOptions = categoryRepository.findAllProductOptionsByCategoryNameHierarchy("For Woman");

注意事项

  • 递归CTE需要数据库版本支持,如果使用MySQL 5.x这类不支持的版本,可以考虑在Category实体中维护一个完整的路径字段(比如path字段存储/父分类ID/子分类ID/),然后通过模糊查询匹配路径;
  • 你的Category父子关系用了@OneToOne,但实际场景中分类通常是一个父分类对应多个子分类,建议改成@ManyToOne关联父分类,@OneToMany关联子分类,这样更符合业务逻辑,递归查询依然适用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:01:53