简体   繁体   English

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

[英]Does cascade=“all-delete-orphan” have any meaning in a Hibernate unidirectional many-to-many association with a join table?

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: 按照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>

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 speakers table (as a Speaker with no associated conferences doesn't have much meaning in my project). 我希望单个发言者可以与许多不同的会议相关联,而且任何会议不再引用的speakersspeakers表中删除(因为没有相关会议的发言人在我的会议中没有多大意义)项目)。

However, I've found that if I use cascade="all-delete-orphan" , then if a Speaker which is associated with multiple Conferences is removed from just one of them , Hibernate attempts to delete the Speaker instance itself. 但是,我发现如果我使用cascade="all-delete-orphan" ,那么如果其中一个会话中删除与多个会议相关联的扬声器,则Hibernate会尝试删除Speaker实例本身。

Below is a unit test which shows this behavior: 下面是一个显示此行为的单元测试:

@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());
}

An error is thrown when s 's removal from c1.speakers is processed, because Hibernate is deleting both the row in the join table and the speakers table row as well: 当发生错误时s的从去除c1.speakers因为Hibernate被删除无论在连接表的行和处理, speakers的表行,以及:

DEBUG org.hibernate.SQL - delete from conference_speakers where conference_id=? DEBUG org.hibernate.SQL - 从conference_speakers中删除conference_id =? and speaker_id=? 和speaker_id =?
DEBUG org.hibernate.SQL - delete from speakers where id=? DEBUG org.hibernate.SQL - 从id =?的扬声器中删除

If I change cascade="all-delete-orphan" to just cascade="all" , then this test works as expected, although it leads to the undesired behavior where I will end up with orphaned rows in my speakers table. 如果我将cascade="all-delete-orphan"改为cascade="all" ,那么这个测试按预期工作,虽然它会导致不希望的行为,我将在speakers表中找到孤立的行。

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 Session )? 这让我想知道 - Hibernate甚至可能知道何时从关系的子端删除孤立对象,但只有在子级未被任何其他父级引用时(无论这些父级是否在当前Session )? Perhaps I am misusing cascade="all-delete-orphan" ? 也许我在滥用cascade="all-delete-orphan"

I get the same exact behavior if I use JPA annotations instead of XML mapping such as: 如果我使用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>();

This is with Hibernate 3.6.7.Final, by the way. 顺便说一句,这是Hibernate 3.6.7.Final。

DELETE_ORPHAN cascade mode is not defined for many-to-many relationship - only for one-to-many (the latter sports a "orphanRemoval=true|false" attribute within JPA standard @OneToMany annotation, so you don't have to resort to proprietary Hibernate annotation). DELETE_ORPHAN级联模式没有为多对多关系定义 - 仅适用于一对多(后者在JPA标准@OneToMany注释中运行“orphanRemoval = true | false”属性,因此您不必诉诸于专有的Hibernate注释)。

The reason for this is exactly as you've described - there's no way for Hibernate to figure out whether "orphaned" end of the many-to-many relationship is truly orphaned without running a query against the database which is both counter-intuitive and can (potentially) have serious performance implications. 其原因正如您所描述的那样 - Hibernate没有办法弄清楚多对多关系的“孤立”结尾是否真正孤立而不对数据库运行查询,这既反直觉又可能(可能)具有严重的性能影响。

Hibernate behavior you've described is therefore correct (well, "as documented"); 因此,您所描述的Hibernate行为是正确的(好吧,“如文档所述”); though in a perfect world it would have alerted you to the fact that DELETE_ORPHAN is illegal on many-to-many during 2nd pass mappings compilation. 虽然在一个完美的世界中,它会提醒你DELETE_ORPHAN在第二次传递映射编译期间在多对多方面是非法的。

I can't think of a good way of achieving what you want to do, to be honest. 说实话,我想不出一个实现你想做的好方法。 The easiest (but database-specific) way would likely be to define a trigger on deletion from conference_speakers that would check whether this speaker is "truly" orphaned and delete it from speakers if so. 最简单(但特定于数据库)的方式可能是定义从conference_speakers删除的触发器,该触发器将检查该扬声器是否“真正”孤立并且如果是这样将其从speakers删除。 The database-independent option is to do the same thing manually in DAO or listener. 与数据库无关的选项是在DAO或侦听器中手动执行相同操作。

Update: Here's an excerpt from Hibernate docs (Chapter 11.11, right after gray Note on CascadeType.ALL), highlights are mine: 更新:这里是Hibernate文档的摘录(第11.11节,紧跟在CascadeType.ALL上的灰色注释之后),重点是我的:

A special cascade style, delete-orphan, applies only to one-to-many associations , and indicates that the delete() operation should be applied to any child object that is removed from the association. 特殊的级联样式delete-orphan仅适用于一对多关联 ,并指示delete()操作应应用于从关联中删除的任何子对象。

Further down: 再向下:

It does not usually make sense to enable cascade on a many-to-one or many-to-many association. 在多对一或多对多关联上启用级联通常没有意义。 In fact the @ManyToOne and @ManyToMany don't even offer a orphanRemoval attribute. 实际上@ManyToOne和@ManyToMany甚至不提供orphanRemoval属性。 Cascading is often useful for one-to-one and one-to-many associations. 级联通常对一对一和一对多关联有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM