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

如何在避免递归溢出时返回带root_parent_id的层级Person数据?

问题描述

我需要在接口响应中包含root_parent_id字段,但该字段对应的rootParent属性是同实体关联,为避免StackOverflowError(无限递归)我给它加了@JsonIgnore注解。下面是当前的响应结果、期望的响应结果,以及我的相关代码和初始化数据,求指导实现方案。

当前响应

[
    {
        "id": 1,
        "full_name": "William",
        "children": [
            {
                "id": 2,
                "full_name": "Henry",
                "children": [
                    {
                        "id": 3,
                        "full_name": "Matt",
                        "children": [
                            {
                                "id": 7,
                                "full_name": "Sophi",
                                "children": []
                            }
                        ]
                    },
                    {
                        "id": 4,
                        "full_name": "Alisa",
                        "children": [
                            {
                                "id": 6,
                                "full_name": "Alexa",
                                "children": []
                            }
                        ]
                    }
                ]
            },
            {
                "id": 5,
                "full_name": "May",
                "children": []
            }
        ]
    },
    {
        "id": 8,
        "full_name": "Olivia",
        "children": [
            {
                "id": 9,
                "full_name": "John",
                "children": [
                    {
                        "id": 11,
                        "full_name": "Oliver",
                        "children": []
                    },
                    {
                        "id": 12,
                        "full_name": "Mia",
                        "children": []
                    }
                ]
            },
            {
                "id": 10,
                "full_name": "Mary",
                "children": [
                    {
                        "id": 13,
                        "full_name": "Evelyn",
                        "children": []
                    }
                ]
            }
        ]
    }
]

期望响应

[
    {
        "id": 1,
        "full_name": "William",
        "root_parent_id": null,
        "children": [
            {
                "id": 2,
                "full_name": "Henry",
                "root_parent_id": 1,
                "children": [
                    {
                        "id": 3,
                        "full_name": "Matt",
                        "root_parent_id": 1,
                        "children": [
                            {
                                "id": 7,
                                "full_name": "Sophi",
                                "root_parent_id": 1,
                                "children": []
                            }
                        ]
                    },
                    {
                        "id": 4,
                        "full_name": "Alisa",
                        "root_parent_id": 1,
                        "children": [
                            {
                                "id": 6,
                                "full_name": "Alexa",
                                "root_parent_id": 1,
                                "children": []
                            }
                        ]
                    }
                ]
            },
            {
                "id": 5,
                "full_name": "May",
                "root_parent_id": 1,
                "children": []
            }
        ]
    },
    {
        "id": 8,
        "full_name": "Olivia",
        "root_parent_id": null,
        "children": [
            {
                "id": 9,
                "full_name": "John",
                "root_parent_id": 8,
                "children": [
                    {
                        "id": 11,
                        "full_name": "Oliver",
                        "root_parent_id": 8,
                        "children": []
                    },
                    {
                        "id": 12,
                        "full_name": "Mia",
                        "root_parent_id": 8,
                        "children": []
                    }
                ]
            },
            {
                "id": 10,
                "full_name": "Mary",
                "root_parent_id": 8,
                "children": [
                    {
                        "id": 13,
                        "full_name": "Evelyn",
                        "root_parent_id": 8,
                        "children": []
                    }
                ]
            }
        ]
    }
]

我的代码

MODEL

@Entity
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({"hibernate_lazy_initializer", "handler"})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Person {

    @Id
    @Getter
    @Setter
    @EqualsAndHashCode.Include
    private Long id;

    @Getter
    @Setter
    private String fullName;

    @ManyToOne(fetch = FetchType.LAZY)
    @Getter
    @Setter
    @JsonIgnore
    private Person parent;

    @ManyToOne(fetch = FetchType.LAZY)
    @Getter
    @Setter
    @JsonIgnore
    private Person rootParent;

    @Transient
    @Getter
    @Setter
    public List<Person> children = new ArrayList<>();

}

REPOSITORY

@Repository
public interface PersonRepo extends JpaRepository<Person, Long> {

    @Query("SELECT p FROM Person p "
            + " WHERE p.parent.id IS NULL")
    List<Person> findRoots();

    @Query("SELECT p FROM Person p"
            + " WHERE p.rootParent.id IN :rootIds ")
    List<Person> findChildrenInRoots(@Param("rootIds") List<Long> rootIds);

}

CONTROLLER

@RestController
@RequestMapping("/api/v1/person")
public class PersonController {

    @Autowired
    private PersonRepo personRepo;

    @GetMapping
    @Transactional(readOnly = true)
    public List<Person> getChildren() {
        List<Person> rootCategories = personRepo.findRoots();

        List<Long> rootCategoryIds = rootCategories.stream().map(Person::getId).collect(Collectors.toList());

        List<Person> children = personRepo.findChildrenInRoots(rootCategoryIds);

        children.forEach(subCategory -> {
            subCategory.getParent().getChildren().add(subCategory);
        });

        return rootCategories;
    }

}

DATA SQL

insert into PERSON values (1,'William',null, null);
insert into PERSON values (2,'Henry',1,1);
insert into PERSON values (3,'Matt',2,1);
insert into PERSON values (4,'Alisa',2,1);
insert into PERSON values (5,'May',1,1);
insert into PERSON values (6,'Alexa',4,1);
insert into PERSON values (7,'Sophi',3,1);
insert into PERSON values (8,'Olivia',null,null);
insert into PERSON values (9,'John',8,8);
insert into PERSON values (10,'Mary',8,8);
insert into PERSON values (11,'Oliver',9,8);
insert into PERSON values (12,'Mia',9,8);
insert into PERSON values (13,'Evelyn',10,8);

解决方案

以下几种方式都能实现需求,同时避免递归问题:

方法1:新增独立的root_parent_id字段(推荐)

直接在实体中添加一个映射数据库root_parent列的字段,不关联实体对象,从根源避免递归:

@Entity
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({"hibernate_lazy_initializer", "handler"})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Person {

    // 原有属性不变...

    // 新增:直接映射数据库列,不关联实体
    @Column(name = "root_parent")
    @Getter
    @Setter
    private Long rootParentId;

    // 保留原关联字段但继续忽略序列化
    @ManyToOne(fetch = FetchType.LAZY)
    @Getter
    @Setter
    @JsonIgnore
    private Person rootParent;

    // 其他代码不变...
}

Jackson会自动将rootParentId序列化为root_parent_id,完全不会触发递归。

方法2:用@JsonGetter手动返回ID

如果不想新增字段,给rootParent添加自定义Getter方法,只返回ID:

@Entity
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({"hibernate_lazy_initializer", "handler"})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Person {

    // 原有属性不变...

    @ManyToOne(fetch = FetchType.LAZY)
    @Getter
    @Setter
    @JsonIgnore
    private Person rootParent;

    // 自定义Getter,返回rootParent的ID
    @JsonGetter("root_parent_id")
    public Long getRootParentId() {
        return rootParent != null ? rootParent.getId() : null;
    }

    // 其他代码不变...
}

这种方式无需修改数据库映射,通过Jackson注解实现字段输出,不会触发递归。

方法3:使用DTO对象(分层架构推荐)

定义专门的DTO类控制响应格式,解耦实体与响应:

public class PersonDTO {
    private Long id;
    private String fullName;
    private Long rootParentId;
    private List<PersonDTO> children = new ArrayList<>();

    // 构造方法:从实体转换为DTO
    public PersonDTO(Person person) {
        this.id = person.getId();
        this.fullName = person.getFullName();
        this.rootParentId = person.getRootParent() != null ? person.getRootParent().getId() : null;
        // 递归转换子节点
        this.children = person.getChildren().stream().map(PersonDTO::new).collect(Collectors.toList());
    }

    // Getter/Setter
}

然后在Controller中返回DTO:

@GetMapping
@Transactional(readOnly = true)
public List<PersonDTO> getChildren() {
    List<Person> rootCategories = personRepo.findRoots();
    List<Long> rootCategoryIds = rootCategories.stream().map(Person::getId).collect(Collectors.toList());
    List<Person> children = personRepo.findChildrenInRoots(rootCategoryIds);

    children.forEach(subCategory -> {
        subCategory.getParent().getChildren().add(subCategory);
    });

    return rootCategories.stream().map(PersonDTO::new).collect(Collectors.toList());
}

这种方式后续修改响应字段更灵活,适合大型项目。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 18:45:32