【问题标题】:Hibernate cascade One to Many doesn't connect child with parent properlyHibernate cascade One to Many没有正确连接孩子和父母
【发布时间】:2018-12-26 15:38:00
【问题描述】:

我有一个 Java API(使用 Spring Framework),我的前端使用它来创建新的父对象 (json),其中包含新的子对象列表,如下所示:

parent = {
    children: [
    {
        childName: 'name1'
    },
    {
        childName: 'name2'
    }]   
}

您可以看到没有 ID,因为我要保存它们,并且数据库应该生成 ID。

Parent 实体在后端有这段代码:

@OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "parent")
private Set<Child> children = new HashSet<>();

在父级上调用存储库“保存”操作后,我可以在数据库中找到子元素(因此级联工作),但数据库中子级的“parentId”列为空,因此当我获取父级,其子级列表显示为空。

我可以通过在我的服务类中做这样的事情来解决这个问题:

repository.save(parent);
parent.getChildren().forEach((child) -> child.setParent(parent));

它之所以有效,是因为(我猜)到那时,父表已经有一个数据库为其生成的 ID,因此子表可以将该 ID 用于其 parentId 列。它可以工作,但感觉这应该是标准的一对多关系,并且应该由框架以比这更漂亮的方式自动处理。所以我的问题是:我错过了什么?

在 Cascade 操作期间保存子实体“parentId”列时,Hibernate 是否可以通过某种方式自动填充它们?

编辑: 这实际上可能是由我用来将 DTO 映射到 POJO 并返回的 MapStruct 引起的。还不确定,但会继续调查(见相关链接:https://github.com/mapstruct/mapstruct/issues/1068

【问题讨论】:

  • 你的猜测是错误的。该实体不存储父级的 ID。它存储对父对象的引用。您可以在保存父级之前执行此操作。但是你必须这样做:这是你的责任,而不是 JPA 的。按规范。
  • 你能在子类中显示你的注释来引用父类吗?您是在子级还是父级上调用保存操作?
  • @Rohit 我保存了父级。 JBNizet 我的意思是子数据库表使用 parent.id 存储父表的外键,但我知道,就像你说的,Java 类具有对整个父对象的引用。我已尝试澄清我的帖子,抱歉。

标签: java spring hibernate jhipster mapstruct


【解决方案1】:

以下是有关如何创建实体和添加子项的一些基础知识。正如您将看到的,您不必遍历子代并在那里设置父代的引用。

public class Parent implements Serializable {
    //bi-directional many-to-one association to Children
    @OneToMany(mappedBy="Parent")
    private List<Children> children;

    public List<Children> getChildren() {
        return children;
    }

    public void setChildren(List<Children> children) {
        this.children= children;
    }

    public Children addChildren(Children child) {
        getChildren().add(child);
        child.setParent(this);

        return child;
    }

    public Children removeChildren(Children child) {
        getChildren().remove(child);
        child.setParent(null);

        return child;
    }
}

现在创建父对象并调用 addChildren 来正确设置引用。希望对您有所帮助。

【讨论】:

  • 这正是我的代码的样子。但是你看;我没有在我的 Java 后端创建父级、子级或将子级添加到父级,它发生在前端代码中。我得到的只是新父级及其新子级列表的完整 json - 直接到我的后端端点。所以我实际上并没有在任何地方调用“addChildren()”,父java类及其java子类都是从json中解析出来的,然后我直接调用“parentRepository.save(parent)”。
【解决方案2】:

我想通了,这真的不是 Hibernate 问题,抱歉。

问题是我使用 MapStruct 来处理更简单的前端对象。这将对象分成 POJO 和它们各自的 DTO 对象。 DTO 对象没有对整个其他对象的引用,它们只是将一些更相关的部分复制到其中以使 json 更简单。

所以 ChildDTO 例如只有

{ 
    name : 'name',
    parentId: '1'
}

然后当调用从前端到后端时使用 MapStruct,将 DTO 转换为真正的 Java 对象,通过数据库中的 id ('ish') 找到父级,并将其添加到子级。但默认情况下,它不知道将父引用添加到没有设置 parentId 的子元素,即使它们是作为父元素的一部分(!)。正因为如此,在 Java Child 类中 - 父引用为空。

至少有两种解决方案:

MapStruct 解决方案:

将自定义映射代码添加到 MapStruct 映射器,以手动设置作为父元素的一部分发送的所有子元素的父引用。见here

Java 解决方案:

在 MapStruct 运行之后,但在保存到数据库之前手动添加对所有子项的父引用:

// MapStruct first maps the DTO to a real Parent object, then:
parent.getChildren().forEach((child) -> child.setParent(parent));
parentRepository.save(parent);

【讨论】:

  • 我已经尝试了第二种解决方案,但它仍然对我不起作用,我在保存期间遇到异常 - org.hibernate.PersistentObjectException:分离的实体传递给坚持:com.invenio.loyalty.rewardservice。 domain.Item 我正在使用 spring data jpa
  • @AniketKalamkar 确保在保存之前已从存储库中获取父级。如果您仅将其作为 java 对象,则将其视为“分离实体”。例如,通过制作 Parent parentFromDatabase = parentRepository.get(parent.id) 那么该对象现在被“附加”并且可以保存。
  • 我发现了问题。由于域和 DTO 之间的名称相同,我的域 DTO 也填充了我的子实体 @id 字段,因此发生这种情况。现在我忽略了该映射,它开始工作正常,因为它是新对象并且 id 为空白,因此 JPA 将其视为持久性并使用生成器生成新值。
【解决方案3】:

很好的解决方案 - 几天来一直遇到同样的问题,我有一个级联的 oneToOne、oneToMany 等,设置来生成一个相当广泛的 xml。我仍然必须向父级添加级联。

GrandParent finalGrandParent = grandparent;
grandparent.getParent().getChildren().forEach(child -> child.setParent(finalGrandParent.getParent()));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-26
    • 1970-01-01
    • 2020-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多