级联= "all-delete-orphan"在与连接表的休眠单向多对多关联中是否有任何意义?



我有两个对象,它们形成父子关系,具有多对多关系。按照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",那么如果与多个会议相关联的Speaker从中删除,只是其中一个, 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());
}

sc1.speakers中移除时抛出错误,因为Hibernate正在删除连接表中的行和speakers表中的行:

DEBUG org.hibernate.SQL - delete from conference_speakers where conference_id=?和speaker_id = ?
delete from speaker where 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。最后,顺便说一下

DELETE_ORPHAN级联模式不是为多对多关系定义的-仅用于一对多(后者在JPA标准@OneToMany注释中具有"orphanRemoval=true|false"属性,因此您不必使用专有的Hibernate注释)。

这样做的原因正如你所描述的那样——Hibernate没有办法判断多对多关系的"孤立"端是否真的是孤立的,除非对数据库运行一个查询,这既违反直觉,又可能(潜在地)有严重的性能影响。

你所描述的Hibernate行为因此是正确的(好吧,"如文档所述");虽然在一个完美的世界里,它会提醒你一个事实,即DELETE_ORPHAN在多对多映射编译期间是非法的。

老实说,我想不出一个实现你想做的事情的好方法。最简单的(但特定于数据库的)方法可能是在从conference_speakers中删除时定义一个触发器,该触发器将检查该发言者是否"真正"为孤儿,如果是,则从speakers中删除该发言者。与数据库无关的选项是在DAO或侦听器中手动执行相同的操作。

更新:这是Hibernate文档(第11.11章,在关于CascadeType.ALL的灰色注释之后)的摘录,亮点是我的:

一个特殊的级联样式,delete-orphan,只适用于一对多关联,并指示delete()操作应该被删除应用于从关联中删除的任何子对象。

进一步:

在多对一或上启用级联通常没有意义多对多关联。事实上,@ManyToOne和@ manymany并没有甚至提供一个孤儿移除属性。级联通常用于一对一和一对多关联。

最新更新