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

基于Spring REST与JPA实现主分类含子分类的JSON返回接口

解决方案:在Spring REST中返回包含子分类的主分类JSON

我来帮你实现这个需求,咱们一步步来:

1. 修改Category实体类,添加子分类关联

首先需要在Category实体里新增子分类的映射关系,同时处理JSON序列化时的循环引用问题:

@Where(clause = "active =1")
@Entity
@Table(name = "category", catalog = "businessin")
public class Category implements java.io.Serializable {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;
    private String name;
    private Integer parentId;
    private Integer active;
    private String pic;

    // 新增:映射子分类集合
    @JsonManagedReference // 标记父端,序列化时会正常输出该字段
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentCategory")
    private List<Category> subCategories = new ArrayList<>();

    // 新增:可选,映射父分类关系(如果业务需要)
    @JsonBackReference // 标记子端,序列化时自动忽略,避免循环引用死循环
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parentId", insertable = false, updatable = false)
    private Category parentCategory;

    @JsonIgnore
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "category")
    private List<Product> products = new ArrayList<Product>();

    // 别忘了生成subCategories和parentCategory的getter/setter方法
}

这里的关键说明:

  • @JsonManagedReference和@JsonBackReference是Jackson用来解决双向关联序列化循环问题的注解,确保JSON生成时不会陷入死循环
  • insertable = false, updatable = false是因为我们已经手动维护了parentId字段,不需要JPA来管理这个外键的插入更新,避免冲突

2. 优化CategoryRepository的查询方法

为了避免N+1查询问题,我们用Fetch Join一次性加载主分类和对应的子分类:

public interface CategoryRepository extends JpaRepository<Category, Integer> {

    // 查询所有主分类(parentId=0),并关联加载子分类
    @Query("SELECT c FROM Category c LEFT JOIN FETCH c.subCategories WHERE c.parentId = 0")
    List<Category> findAllMainCategoriesWithSubCategories();
}

如果不需要一次性加载,也可以用简单的方法命名查询:List<Category> findByParentId(Integer parentId);,但这种方式在访问子分类时会触发懒加载,产生额外的数据库查询,推荐用Fetch Join的方式。

3. 在REST控制器中返回结果

最后在控制器里调用Repository的方法,直接返回主分类列表即可:

@RestController
@RequestMapping("/api/categories")
public class CategoryController {

    private final CategoryRepository categoryRepository;

    // 推荐构造注入,替代@Autowired
    public CategoryController(CategoryRepository categoryRepository) {
        this.categoryRepository = categoryRepository;
    }

    @GetMapping("/main")
    public ResponseEntity<List<Category>> getMainCategories() {
        List<Category> mainCategories = categoryRepository.findAllMainCategoriesWithSubCategories();
        return ResponseEntity.ok(mainCategories);
    }
}

这样返回的JSON结构就会和你期望的一致:

[
    {
        "id": 1,
        "name": "Electronics",
        "parentId": 0,
        "active": 1,
        "pic": null,
        "subCategories": [
            {
                "id": 2,
                "name": "Mobile",
                "parentId": 1,
                "active": 1,
                "pic": null
            },
            {
                "id": 3,
                "name": "Laptops",
                "parentId": 1,
                "active": 1,
                "pic": null
            }
        ]
    }
]

额外可选方案:用DTO解耦实体与返回结构

如果不想修改实体类(比如担心影响现有业务逻辑),可以创建一个DTO类来封装返回数据:

public class CategoryWithSubDTO {
    private Integer id;
    private String name;
    private Integer parentId;
    private Integer active;
    private String pic;
    private List<CategoryWithSubDTO> subCategories;

    // 构造函数、getter/setter,或者用ModelMapper工具快速完成实体到DTO的转换
}

在Service层把Category实体转换为DTO后再返回,这样更灵活,也不会污染实体类的结构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:23:41