Spring Boot Rest/JPA:仅创建一个父实体,在将子实体的创建级联到父实体的创建时

后端开发 2026-07-10

我需要用Spring Boot 3.x与 Spring Data JPA(Hibernate)来创建一个RESTful API。
我有如下场景。

我需要创建子实体,并将持久化/合并操作级联到父实体。
这意味着:
- 创建 "Child_1" 属于 "Parent_1",并级联创建其父实体 "Parent_1"
- 创建 "Child_2" 属于 "Parent_1",并级联创建其父实体 "Parent_1"
请注意,"Child_1"、"Child_2" 是 "Parent_1" 的子对象。
因此,我想要如下所示的场景:

[所需场景]

Child:
id  name        parent_id
1   child_1     1
2   child_2     1

Parent:
id  name
1   parent_1

不幸的是,结果如下:
[结果场景]

Child:
id  name        parent_id
1   child_1     1
2   child_2     2

Parent:
id  name
1   parent_1
2   parent_1

在每次创建子对象时,都会级联创建一个新的父对象。

下面是基础代码。若你需要一个用例,请把Parent/Post、Child/Comment替换为在JPA/Hibernate专家Vlad Mihalcea的示例。

有懂行的人能给我一个解决方案吗?
如何通过从子对象到父对象的级联来实现 [所需场景]?

先行致谢

@RestController
public class ChildController {

    private final ChildService childService; //Autowired

    @PostMapping
    public ChildResponseDto saveChild(@RequestBody ChildRequestDto childRequestDto) {

        ChildResponseDto childResponseDto = childService.saveChild(childRequestDto);

        return childResponseDto;
    }
}

@Service
public class ChildService {

    private final ChildRepository childRepository; // Autowired
    private final ChildMapper childMapper; // Autowired

    public ChildResponseDto saveChild(ChildRequestDto childRequestDto) {
        Child child = childMapper.toChild(childRequestDto);
        Child savedChild = childRepository.save(child);
        return childMapper.fromChild(savedChild);
    }
}

@Service
public class ChildMapper {

    private final ParentMapper parentMapper; // Autowired

    public ChildResponseDto fromChild(Child child) {
        return ChildResponseDto.builder()
                .id(child.getId())
                .name(child.getName())
                .parentResponseDto(parentMapper.fromParent(child.getParent()))
                .build();
    }

    public Child toChild(ChildRequestDto childRequestDto) {
        Parent parent = parentMapper.toParent(childRequestDto.parentRequestDto());
        return Child.builder()
                .name(childRequestDto.name())
                .parent(parent)
                .build();
    }
}

@Entity
public class Parent {

    @Id
    private Integer id;

    private String name;

    @OneToMany(mappedBy = "parent")
    private List<Child> children = new ArrayList<>();
}

@Entity
public class Child {

    @Id
    private Long id;

    private String name;

    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name = "parent_id")
    private Parent parent;
}

解决方案

为什么会这样?

toChild 方法中,你将请求的DTO映射为Parent和 Child对象,然后通过调用 save 方法来持久化Child对象。

Hibernate对实体有生命周期,实体有4 种状态(更多细节请看这里)。 在这个生命周期中,实体会处于不同的状态,Hibernate的实体状态共有4 种:

  1. 临时态
  2. 持久态
  3. 分离态
  4. 移除态

在你的代码中,Parent和 Child实体都处于 Transient 状态,因为它们是新的对象,Hibernate不知道已有的Parent实体,因此当Hibernate尝试持久化这些对象时,会把它们都视为新的对象,然后将它们作为新的记录写入数据库。

解决方案

要解决这个问题,你需要让Hibernate知道数据库中已经存在一个父实体实例,这意味着在将Child实例持久化到数据库之前,应该从数据库中加载Parent实体,而不是新建一个对象。

当你从数据库加载一个Parent实体时,这个Parent实体的状态是 Persistent state 而不是 Transient State,这意味着Hibernate会跟踪对象的状态,也就是说Hibernate知道该实体存在于数据库中,不会为其创建新的实例。

伪代码将大致如下:

Parent parent = parentRepository.load(parentId);
Child child = childMapper.toChild(childRequestDto);
child.setParent(parent);
childRepository.save(child);
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章