У меня есть два объекта, которые образуют отношения родитель-ребенок, которые имеют отношение многие ко многим. Следуя рекомендациям справочного руководства по Hibernate, я сопоставил это с помощью таблицы соединений:
<class name="Conference" table="conferences">
...
<set name="speakers" table="conference_speakers" cascade="all">
<key column="conference_id"/>
<many-to-many class="Speaker" column="speaker_id"/>
</set>
</class>
<class name="Speaker" table="speakers">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="firstName"/>
<property name="lastName"/>
</class>
Мое желание заключается в том, чтобы один докладчик мог быть связан со многими различными конференциями, но также и то, что любой оратор, на который больше не ссылается какая-либо конференция, удаляется из таблицы speakers
(поскольку докладчик без связанных конференций не имеют большое значение в моем проекте).
Однако я обнаружил, что если я использую cascade="all-delete-orphan"
, то, если динамик, связанный с несколькими конференциями, удаляется из только одной из них , Hibernate пытается удалить сам экземпляр Speaker.
Ниже приведен модульный тест, демонстрирующий это поведение:
@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());
}
При обработке удаления s
из c1.speakers
выдается ошибка, поскольку Hibernate удаляет как строку в таблице объединения, так и строку таблицы speakers
:
DEBUG org.hibernate.SQL - удалить из конференц-спикеров, где conference_id =? и speaker_id =?
DEBUG org.hibernate.SQL - удалить из динамиков, где id =?
Если я изменю cascade="all-delete-orphan"
на cascade="all"
, тогда этот тест будет работать, как и ожидалось, хотя и приведет к нежелательному поведению, в результате которого я получу потерянные строки в моей таблице speakers
.
Это заставляет меня задуматься - возможно ли, чтобы Hibernate знал, когда удалять осиротевшие объекты из дочерних отношений, но только тогда, когда другие родители не ссылаются на ребенка (независимо от того, являются ли эти родители в текущем Session
)? Возможно, я неправильно использую cascade="all-delete-orphan"
?
Я получаю точно такое же поведение, если использую аннотации JPA вместо сопоставления XML, например:
@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<Speaker> speakers = new HashSet<Speaker>();
Это с Hibernate 3.6.7. Кстати, между прочим.