10

我有两个对象,它们形成了具有多对多关系的父子关系。按照 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 会尝试删除发言人实例本身。

以下是显示此行为的单元测试:

@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_speakers 中删除conference_id=?和speaker_id=?
调试 org.hibernate.SQL - 从 id=? 的扬声器中删除

如果我更改cascade="all-delete-orphan"为 just 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.Final 的。

4

1 回答 1

15

没有为多对多关系定义 DELETE_ORPHAN 级联模式 - 仅针对一对多(后者在 JPA 标准@OneToMany注释中带有“orphanRemoval=true|false”属性,因此您不必求助于专有的 Hibernate注解)。

其原因与您所描述的完全一样 - Hibernate 无法确定多对多关系的“孤立”端是否真的是孤立的,而无需对数据库运行查询,这既违反直觉又可以(可能)对性能产生严重影响。

因此,您描述的休眠行为是正确的(嗯,“如文档所述”);尽管在一个完美的世界中,它会提醒您DELETE_ORPHAN在第二遍映射编译期间多对多是非法的。

老实说,我想不出实现你想做的事情的好方法。最简单(但特定于数据库)的方法可能是定义删除触发器conference_speakers,以检查该扬声器是否“真正”孤立,如果是,则将其删除speakers。独立于数据库的选项是在 DAO 或侦听器中手动执行相同的操作。

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

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

再向下:

在多对一或多对多关联上启用级联通常没有意义。事实上,@ManyToOne 和 @ManyToMany 甚至不提供 orphanRemoval 属性。级联通常对一对一和一对多关联很有用。

于 2011-09-30T17:54:01.323 回答