使双向父子集合保持最新

时间:2015-01-12 14:38:09

标签: java hibernate jpa

我试图使用内部数据库结构保持实体的集合是最新的,但是没有在父和子之间使用双向,级联删除关系。

  • 删除父级应该级联删除所有子级
  • 添加和删除儿童应反映在父母的getChildren()

如果只有一个子节点,下面的代码可以工作,我得到ConcurrentModificationException,这是合乎逻辑的,因为Hibernate在级联时迭代了集合。

如果我删除@PreRemove下面的 removeChild 测试失败。

有关如何在不添加执行清理的特定deleteChild方法的情况下解决此问题的任何建议?我试图避免在实体之外使用任何清理方法。

@Entity
public class Parent {
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children = new HashSet<>();

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

    void internalAddChild(final Child child) {
        children.add(child);
    }

    void internalRemoveChild(final Child child) {
        children.remove(child);
    }
}

@Entity
public class Child {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id", nullable = false)
    private Parent parent;

    public Child(final Parent parent) {
        setParent(parent);
    }

    public final void setParent(final Parent parent) {
        if (this.parent != null) {
            this.parent.internalRemoveChild(this);
        }

        this.parent = parent;

        if (parent != null) {
            parent.internalAddChild(this);
        }
    }

    @PreRemove
    private void preRemove() {
        // Causes ConcurrentModificationException in test removeParent below
        if (parent != null) {
            parent.internalRemoveChild(this);
        }
    }
}   

试验:

@Test
public void removeParent() {
    EntityManager em = getEntityManager()
    Parent parent = new Parent();
    em.persist(parent);
    em.persist(new Child(parent));
    em.persist(new Child(parent));

    assertTrue(parent.getChildren().size() == 2);

    // Causes ConcurrentModificationException if more than 1 child
    em.remove(parent);

    // Both children should be deleted
}

@Test
public void removeChild() {
    EntityManager em = getEntityManager()
    Parent parent = new Parent();
    em.persist(parent);

    Child child = new Child(parent);
    em.persist(child);

    em.remove(child);

    // Fails without @PreRemove in Child, child is still present in set
    assertFalse(parent.getChildren().contains(child));
}

异常堆栈跟踪:

java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
    at java.util.HashMap$KeyIterator.next(HashMap.java:1453)
    at org.hibernate.collection.internal.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:789)
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:379)
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:319)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:296)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:118)
    at org.hibernate.event.internal.DefaultDeleteEventListener.cascadeBeforeDelete(DefaultDeleteEventListener.java:353)
    at org.hibernate.event.internal.DefaultDeleteEventListener.deleteEntity(DefaultDeleteEventListener.java:275)
    at org.hibernate.event.internal.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:160)
    at org.hibernate.event.internal.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:73)
    at org.hibernate.internal.SessionImpl.fireDelete(SessionImpl.java:920)
    at org.hibernate.internal.SessionImpl.delete(SessionImpl.java:896)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.remove(AbstractEntityManagerImpl.java:1214)
    ...

1 个答案:

答案 0 :(得分:0)

尝试将orphanRemoval = true放在@OneToMany映射上,然后删除CascadeType.REMOVE,因为它现在是多余的。这指示持久性提供程序在删除父级时删除子实体,或者将它们的关系设置为null。

一方注意(可能会影响这个问题,但不是必须的,这只是一个好习惯)是避免在构造函数中连接关系(就像你现在在Child中所做的那样) ,而是将逻辑移动到某种addChildremoveChild方法(internalRemoveChildinternalAddChild在您的情况下)。它看起来像这样

void internalAddChild(final Child child) {
    if (child != null) {
        child.setParent(this);
        children.add(child);
    }
}

void internalRemoveChild(final Child child) {
    if (child != null) {
        children.remove(child);
        child.setParent(null);
    }
}

// test code

Parent parent = new Parent();
Child c1 = new Child();
Child c2 = new Child();
parent.internalAddChild(c1);
parent.internalAddChild(c2);
em.persist(parent);
em.persist(c1);
em.persist(c2);