我试图使用内部数据库结构保持实体的集合是最新的,但是没有在父和子之间使用双向,级联删除关系。
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)
...
答案 0 :(得分:0)
尝试将orphanRemoval = true
放在@OneToMany
映射上,然后删除CascadeType.REMOVE
,因为它现在是多余的。这指示持久性提供程序在删除父级时删除子实体,或者将它们的关系设置为null。
一方注意(可能会影响这个问题,但不是必须的,这只是一个好习惯)是避免在构造函数中连接关系(就像你现在在Child
中所做的那样) ,而是将逻辑移动到某种addChild
和removeChild
方法(internalRemoveChild
和internalAddChild
在您的情况下)。它看起来像这样
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);