【问题标题】:Cascading a Child entity persist operation to its Parent entity将子实体持久化操作级联到其父实体
【发布时间】:2015-04-15 15:23:55
【问题描述】:

我有一个OneToMany 与我使用 Hibernate 注释设置的两个实体的关联。此关联的Child 实体有一个复合主键,它由外键parent 列和另一个标识符childName 组成。当我尝试通过保存子实体将提交级联到父级时,这似乎会导致“违反参照完整性约束”。

我已经创建了一个简单的问题示例,该示例从我使用这种关系建模的实际场景中抽象出来,但这意味着我知道问题是由于这种关联和使用复合主键造成的。

为什么我不能通过保存子实体来提交两个实体?为什么会违反外键约束?

主要测试方法*:

// Create some detached entities
Parent p = new Parent();
p.setName("Fooson");

// New child id
Child.ChildPK pk = new Child.ChildPK();
pk.setParentName(p);
pk.setChildName("Barty");     

// Set id to new Child
Child c = new Child();
c.setChildPK(pk);

// Add child to parent
p.getChildren().add(c);

// Saving the parent
// service.parentDao.save(p); // if this is uncommented, it works fine

// Cascade child and associated parents in one transaction
service.childDao.save(c); // ConstraintViolationException

子.java

@Entity
@Table(name="Child")
public class Child implements Serializable {

    @EmbeddedId
    private ChildPK id;

    public ChildPK getChildPK() {
        return id;
    }

    public void setChildPK(ChildPK childPK) {
        this.id = childPK;
    }

    @Embeddable
    public static class ChildPK implements Serializable 
    {
        @ManyToOne(cascade=CascadeType.ALL)
        @JoinColumn(name="name")
        Parent parent;

        @Column(name="childName")
        String childName;

        public Parent getParentName() {
            return parent;
        }

        public void setParentName(Parent parentName) {
            this.parent = parentName;
        } 

        public String getChildName() {
            return childName;
        }

        public void setChildName(String childName) {
            this.childName = childName;
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 67 * hash + Objects.hashCode(this.parent);
            hash = 67 * hash + Objects.hashCode(this.childName);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ChildPK other = (ChildPK) obj;
            if (!Objects.equals(this.parent, other.parent)) {
                return false;
            }
            return Objects.equals(this.childName, other.childName);
        }

        @Override
        public String toString() {
            return "ChildPK{" + "parentName=" + parent.getName() + ", childName=" + childName + '}';
        }

    }

    @Override
    public String toString() {
        return "Child{" + "id=" + id + '}';
    }

}

父类.java

@Entity
@Table(name="Parent")
public class Parent implements Serializable {

    @Id
    @Column(name="name")
    private String name;

    @OneToMany(mappedBy="id.parentName", cascade=CascadeType.ALL)
    Set<Child> children = new HashSet<>(0);

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Child> getChildren() {
        return children;
    }

    public void setChildren(Set<Child> children) {
        this.children = children;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 73 * hash + Objects.hashCode(this.name);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Parent other = (Parent) obj;
        if (!Objects.equals(this.name, other.name)) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "Parent{" + "name=" + name + ", children=" + children + '}';
    }

}

完整的异常信息是:

Exception in thread "main" org.hibernate.exception.ConstraintViolationException: 
Could not execute JDBC batch update
...
Caused by: org.h2.jdbc.JdbcBatchUpdateException: 
Referential integrity constraint violation: 
"FK3E104FCBA07683D: PUBLIC.CHILD FOREIGN KEY(NAME) REFERENCES 
PUBLIC.PARENT(NAME) ('Fooson')"; SQL statement:
insert into Child (childName, name) values (?, ?)
...

* main 方法指的是 DAO 和 ServiceLayer 对象,我没有在这里展示,因为我认为它与问题无关。但是,如果需要,我可以发布这些内容。

【问题讨论】:

  • 复合主键与父主键有一个连接列,这意味着父必须存在才能创建子,当您尝试在没有父的情况下保存子时,则抛出异常。始终必须先保存父级,或使用从 db HQL 检索到的现有父级

标签: java hibernate jpa hibernate-mapping composite-primary-key


【解决方案1】:

将保存操作从子实体级联到父实体没有意义,反之亦然。

那是因为操作将是:

  • 救救孩子
  • 将保存操作传播给 Parent

但是 Child 依赖于 Parent.id 来设置其 FK。

您也需要从数据库的角度来可视化这个操作流程。您可以保存引用不存在的父母的孩子吗?当然不是。

想想当你删除一个子实体时会发生什么。级联将传播到父级,然后传播到其所有子级。

因此,您必须从 Embeddable 类中移除级联:

@ManyToOne(cascade=CascadeType.ALL)

并取消注释可以正常工作的代码:

// Saving the parent
service.parentDao.save(p); // if this is uncommented, it works fine

【讨论】:

  • 感谢您的回答,这很有意义。为我的 Child 实体创建一个包含对我的 Parent 的外部引用的主键对我来说甚至是一个好主意吗?一种替代方法是在 ID 中使用不受引用约束的单独列,但保持与外键列相同。这将使代码更加解耦,但我不知道这是否是一件好事。
  • 有一个引用父 PK 的子 PK 列很好。您甚至可以将其作为复合 ID,以允许每个父级有多个子级。不要忘记将答案标记为已接受。
  • @MattLBeck。由父 Pk(加上一列)形成的子 PK 是关系型的,正常的,它具有完整性。 ID Pk 没有完整性,它是非关系的。
猜你喜欢
  • 2016-05-17
  • 2012-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-15
  • 1970-01-01
  • 2015-11-02
相关资源
最近更新 更多