title: Does cascade="all-delete-orphan" have any meaning in a Hibernate unidirectional many-to-many association with a join table?
body: <p>I have two objects which form a parent-child relationship which have a many-to-many relationship. Following the recommendations in the Hibernate reference manual, I have mapped this using a join table:</p>
  
  <pre><code>&lt;class name="Conference" table="conferences"&gt;
      ...
      &lt;set name="speakers" table="conference_speakers" cascade="all"&gt;
          &lt;key column="conference_id"/&gt;
          &lt;many-to-many class="Speaker" column="speaker_id"/&gt;
      &lt;/set&gt;
  &lt;/class&gt;
  
  &lt;class name="Speaker" table="speakers"&gt;
      &lt;id name="id" column="id"&gt;
          &lt;generator class="native"/&gt;
      &lt;/id&gt;
      &lt;property name="firstName"/&gt;
      &lt;property name="lastName"/&gt;
  &lt;/class&gt;
  </code></pre>
  
  <p>My wish is that a single Speaker can be associated with many different Conferences, but also that any Speaker which is no longer referenced by any Conference is removed from the <code>speakers</code> table (as a Speaker with no associated conferences doesn't have much meaning in my project).</p>
  
  <p>However, I've found that if I use <code>cascade="all-delete-orphan"</code>, then if a Speaker which is associated with multiple Conferences is removed from <em>just one of them</em>, Hibernate attempts to delete the Speaker instance itself.</p>
  
  <p>Below is a unit test which shows this behavior:</p>
  
  <pre><code>@Test
  public void testRemoveSharedSpeaker() {
  
      int initialCount = countRowsInTable("speakers");
  
      Conference c1 = new Conference("c1");
      Conference c2 = new Conference("c2");
  
      Speaker s = new Speaker("John", "Doe");
  
      c1.getSpeakers().add(s);
      c2.getSpeakers().add(s);
  
      conferenceDao.saveOrUpdate(c1);
      conferenceDao.saveOrUpdate(c2);
      flushHibernate();
  
      assertEquals(initialCount + 1, countRowsInTable("speakers"));
      assertEquals(2, countRowsInTable("conference_speakers"));
  
      // the remove:
      c1 = conferenceDao.get(c1.getId());
      c1.getSpeakers().remove(s);
  
      flushHibernate();
  
      assertEquals("count should stay the same", initialCount + 1, countRowsInTable("speakers"));
      assertEquals(1, countRowsInTable("conference_speakers"));
  
      c1 = conferenceDao.get(c1.getId());
      c2 = conferenceDao.get(c2.getId());
  
      assertEquals(0, c1.getSpeakers().size());
      assertEquals(1, c2.getSpeakers().size());
  }
  </code></pre>
  
  <p>An error is thrown when <code>s</code>'s removal from <code>c1.speakers</code> is processed, because Hibernate is deleting both the row in the join table and the <code>speakers</code> table row as well:</p>
  
  <blockquote>
    <p>DEBUG org.hibernate.SQL - delete from conference_speakers where conference_id=? and speaker_id=?<br>
    DEBUG org.hibernate.SQL - delete from speakers where id=?</p>
  </blockquote>
  
  <p>If I change <code>cascade="all-delete-orphan"</code> to just <code>cascade="all"</code>, then this test works as expected, although it leads to the undesired behavior where I will end up with orphaned rows in my <code>speakers</code> table.</p>
  
  <p>This leads me to wonder - is it even possible for Hibernate to know when to delete orphaned objects from the child-side of the relationship, but only when the child is not referenced by any other parents (whether or not those parents are in the current <code>Session</code>)? Perhaps I am misusing <code>cascade="all-delete-orphan"</code>?</p>
  
  <p>I get the same exact behavior if I use JPA annotations instead of XML mapping such as:</p>
  
  <pre><code>@ManyToMany(cascade = CascadeType.ALL)
  @JoinTable(name = "conference_speakers",
          joinColumns = @JoinColumn(name = "conference_id"),
          inverseJoinColumns = @JoinColumn(name = "speaker_id"))
  @org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
  private Set&lt;Speaker&gt; speakers = new HashSet&lt;Speaker&gt;();
  </code></pre>
  
  <p>This is with Hibernate 3.6.7.Final, by the way.</p>