cascade =“all-delete-orphan”在与连接表的Hibernate单向多对多关联中有什么意义吗?

时间:2011-09-30 17:30:20

标签: java hibernate many-to-many cascade 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",那么如果从只删除其中一个会议中删除与多个会议相关联的扬声器,则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());
}

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

  

DEBUG org.hibernate.SQL - 从conference_speakers中删除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.Final。

1 个答案:

答案 0 :(得分:13)

DELETE_ORPHAN级联模式没有为多对多关系定义 - 仅适用于一对多(后者在JPA标准@OneToMany注释中运行“orphanRemoval = true | false”属性,所以你不要不得不求助于专有的Hibernate注释。

原因正如您所描述的那样 - Hibernate没有办法弄清楚多对多关系的“孤立”结尾是否真正成为孤儿而没有对数据库运行查询 - 直观且可能(可能)具有严重的性能影响。

您所描述的Hibernate行为因此是正确的(好吧,“如文档所述”);虽然在一个完美的世界中,它会提醒你{2}在第二遍映射编译过程中DELETE_ORPHAN是非法的事实。

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

更新:以下摘自Hibernate docs(第11.11节,紧接着CascadeType.ALL的灰色注释),重点是我的:

  

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

再向下:

  

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