I am trying to keep the collections of the entities up to date with the internal database structure but failing to do so with a bidirectional, cascade-delete relation between Parent and Child.
getChildren()
set The code below works if there is only one child, any more than that and I get ConcurrentModificationException
, which is logical since Hibernate iterates over the collection when cascading.
If I remove the @PreRemove
the removeChild test below fails.
Any suggestions on how to solve this without adding a specific deleteChild method that performs the clean up? I am trying to avoid having any clean-up methods outside of the entities.
@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);
}
}
}
Tests:
@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));
}
Exception stack trace:
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)
...
Try putting orphanRemoval = true
on @OneToMany
mapping, and remove CascadeType.REMOVE
since it is now redundant. This instructs the persistence provider to remove child entities when parent is deleted, or their relation is set to null.
One side note (may affect this problem but doesn't have to, it's just good practice) is to avoid wiring up the relations in the constructor (like you do now in Child
, and instead move the logic to some kind of addChild
and removeChild
methods ( internalRemoveChild
and internalAddChild
in your case). It would look like this
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);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.