title: Keeping a bidirectional parent-child collection up to date
body: <p>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. </p>
  
  <ul>
  <li>Deleting a parent should cascade-delete all children</li>
  <li>Addition and deletion of a child should be reflected in the parent's <code>getChildren()</code> set</li>
  </ul>
  
  <p>The code below works if there is only one child, any more than that and I get <code>ConcurrentModificationException</code>, which is logical since Hibernate iterates over the collection when cascading.</p>
  
  <p>If I remove the <code>@PreRemove</code> the <em>removeChild</em> test below fails. </p>
  
  <p>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.</p>
  
  <pre><code>@Entity
  public class Parent {
      @OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", cascade = CascadeType.REMOVE)
      private Set&lt;Child&gt; children = new HashSet&lt;&gt;();
  
      public Set&lt;Child&gt; 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);
          }
      }
  }   
  </code></pre>
  
  <p><strong>Tests:</strong></p>
  
  <pre><code>@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));
  }
  </code></pre>
  
  <p>Exception stack trace:</p>
  
  <pre><code>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)
      ...
  </code></pre>