Cascade = “all-delete-orphan” a-t-il une signification dans une association plusieurs-à-plusieurs unidirectionnelle Hibernate avec une table de jointure?

J’ai deux objects qui forment une relation parent-enfant qui a une relation plusieurs-à-plusieurs. En suivant les recommandations du manuel de référence d’Hibernate, j’ai mappé cela à l’aide d’une table de jointure:

 ...             

Mon souhait est qu’un seul président puisse être associé à de nombreuses conférences différentes, mais également qu’un président qui n’est plus référencé par une conférence soit retiré de la table des speakers (un président sans conférence associée n’a pas beaucoup de sens dans mon cas. projet).

Cependant, j’ai constaté que si j’utilisais cascade="all-delete-orphan" , alors si un haut-parleur associé à plusieurs conférences est supprimé de l’un d’entre eux , Hibernate tente de supprimer l’instance de haut-parleur elle-même.

Ci-dessous, un test unitaire qui montre ce comportement:

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

Une erreur est c1.speakers lorsque la suppression de s de c1.speakers est traitée, car Hibernate supprime à la fois la ligne de la table de jointure et la ligne de la table de speakersspeakers :

DEBUG org.hibernate.SQL – Supprimer de conference_speakers où conference_id =? et speaker_id =?
DEBUG org.hibernate.SQL – Supprimer des haut-parleurs où id =?

Si je modifie cascade="all-delete-orphan" en cascade="all" , le test fonctionne comme prévu, bien qu’il conduise au comportement non souhaité dans lequel je vais me retrouver avec des lignes orphelines dans la table des speakersspeakers .

Cela me conduit à me demander: est-il possible pour Hibernate de savoir quand supprimer des objects orphelins du côté enfant de la relation, mais uniquement lorsque l’enfant n’est pas référencé par d’autres parents (que ces parents soient ou non dans la liste actuelle)? Session )? Peut-être que j’utilise mal cascade="all-delete-orphan" ?

J’ai le même comportement si j’utilise des annotations JPA au lieu d’un mappage XML tel que:

 @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 speakers = new HashSet(); 

C’est avec Hibernate 3.6.7.Final, au fait.

    Le mode cascade DELETE_ORPHAN n’est pas défini pour les relations plusieurs-à plusieurs – uniquement pour les relations un-à-plusieurs (ce dernier affiche un atsortingbut “orphanRemoval = true | false” dans l’annotation @OneToMany norme JPA, de sorte que vous n’avez pas à recourir à annotation propriétaire Hibernate).

    La raison en est exactement celle que vous avez décrite – Hibernate n’a aucun moyen de déterminer si la fin “orpheline” de la relation plusieurs-à-plusieurs est véritablement orpheline sans lancer une requête contre la firebase database à la fois contre-intuitive et intuitive. peut (potentiellement) avoir de graves conséquences sur les performances.

    Le comportement Hibernate que vous avez décrit est donc correct (bien, “comme documenté”); bien que dans un monde parfait, il vous aurait alerté sur le fait que DELETE_ORPHAN est illégal dans la compilation de plusieurs mappages 2nd Pass.

    Pour être honnête, je ne peux pas penser à un bon moyen de réaliser ce que vous voulez faire. Le moyen le plus simple (mais spécifique à la firebase database) serait probablement de définir un déclencheur lors de la suppression de conference_speakers qui vérifierait si ce locuteur est “véritablement” orphelin et le supprimerait si tel était le cas. L’option indépendante de la firebase database consiste à faire la même chose manuellement dans DAO ou listener.

    Mise à jour: Voici un extrait de la documentation d’Hibernate (chapitre 11.11, juste après une note grise sur CascadeType.ALL), les points saillants sont les miens:

    Un style de cascade spécial, delete-orphan, s’applique uniquement aux associations un-à-plusieurs et indique que l’opération delete () doit être appliquée à tout object enfant supprimé de l’association.

    Plus bas:

    Habituellement, il n’est pas logique d’activer la cascade sur une association plusieurs-à-un ou plusieurs-à-plusieurs. En fait, les @ManyToOne et @ManyToMany n’offrent même pas d’atsortingbut orphanRemoval. La mise en cascade est souvent utile pour les associations un à un et un à plusieurs.