Hibernate insère les doublons dans un @OneToMany collection
J'ai une question concernant Hibernate 3.6.7 et JPA 2.0.
Envisager des entités suivantes (certains getters et les setters sont omis par souci de concision):
@Entity
public class Parent {
@Id
@GeneratedValue
private int id;
@OneToMany(mappedBy="parent")
private List<Child> children = new LinkedList<Child>();
@Override
public boolean equals(Object obj) {
return id == ((Parent)obj).id;
}
@Override
public int hashCode() {
return id;
}
}
@Entity
public class Child {
@Id
@GeneratedValue
private int id;
@ManyToOne
private Parent parent;
public void setParent(Parent parent) {
this.parent = parent;
}
@Override
public boolean equals(Object obj) {
return id == ((Child)obj).id;
}
@Override
public int hashCode() {
return id;
}
}
Considérons maintenant ce morceau de code:
//persist parent entity in a transaction
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Parent parent = new Parent();
em.persist(parent);
int id = parent.getId();
em.getTransaction().commit();
em.close();
//relate and persist child entity in a new transaction
em = emf.createEntityManager();
em.getTransaction().begin();
parent = em.find(Parent.class, id);
//*: parent.getChildren().size();
Child child = new Child();
child.setParent(parent);
parent.getChildren().add(child);
em.persist(child);
System.out.println(parent.getChildren()); //-> [Child@1, Child@1]
em.getTransaction().commit();
em.close();
L'entité enfant est mal inséré deux fois dans la liste des enfants de l'entité mère.
Lorsque vous effectuez l'une des opérations suivantes, le code fonctionne très bien (pas de doublons dans la liste):
- supprimer la
mappedBy
attribut dans l'entité mère - effectuer certaines opérations de lecture de sur la liste des enfants (par exemple, décommentez la ligne marquée par
*
)
Ceci est bien évidemment un comportement bizarre. Aussi, lors de l'utilisation de EclipseLink comme le fournisseur de persistance, le code fonctionne comme prévu (pas de doublons).
Est-ce une Hibernate bug ou ai-je raté quelque chose?
Grâce
- Pourriez-vous ajouter le code de la setParent de la méthode et de l'est égal à/hashCode méthodes?
- Je viens d'ajouter les méthodes que vous avez demandé. Cependant, je ne pense pas que ce problème est lié à égale / hashCode.
- Vos égaux méthodes ne respectent pas le contrat de l'Objet.d'égal à égal. En outre, le hashCode changements lorsque l'ID est généré et attribué à l'entité. Je ne serais pas surpris si le bug a disparu une fois que vous retirez hashCode et equals. BTW. Hibernate ne recommande pas l'utilisation de l'ID d'implémenter equals et hashCode.
- Merci pour votre commentaire. Je n'étais pas au courant de la controverse de l'aide de l'identifiant persistant dans equals et hashCode. Mais même si j'ai supprimer mon equals et hashCode implémentations, le problème reste le même.
Vous devez vous connecter pour publier un commentaire.
C'est un bug en mode veille prolongée. Étonnamment, il n'est pas signalée encore, hésitez pas à le signaler.
Opérations contre les non-initialisé paresseux, les collections sont mises en file d'attente pour les exécuter après la collecte est initialisé, et Hibernate ne pas gérer la situation lorsque ces opérations conflit avec les données de la base de données. Habituellement, il n'est pas un problème, parce que cette file d'attente est effacé lors de la
flush()
, et les éventuels conflits de modifications sont propagées à la base de données surflush()
ainsi. Cependant, certains changements (comme la persistance des entités avec l'id généré par le générateur de typeIDENTITY
, je suppose que c'est votre cas) sont propagées à la base de données sans la pleineflush()
, et dans ces cas, les conflits sont possibles.Comme une solution de contournement, vous pouvez
flush()
la session après la persistance de l'enfant:J'ai résolu ce problème en disant à Hibernate de ne pas ajouter de doublons dans ma collection. Dans votre cas, changer le type de votre
children
champ deList<Child>
àSet<Child>
et de mettre en œuvreequals(Object obj)
ethashCode()
sur leChild
classe.Évidemment, cela ne sera pas possible dans tous les cas, mais si il y a une façon saine d'identifier qu'un
Child
instance est unique, alors cette solution peut être relativement indolore.Je suis tombé sur cette question lorsque j'avais des questions, non avec l'ajout d'éléments à une liste annotée avec @OneToMany, mais lorsque j'essaie d'itérer sur les éléments d'une telle liste. Les éléments de la liste ont toujours été dupliqué, parfois beaucoup plus que deux fois. (S'est également produit lors de la annotée avec @ManyToMany). L'aide d'un Jeu n'est pas une solution ici, étant donné que ces listes étaient censés permettre de dupliquer des éléments en eux.
Exemple:
Comme il s'est avéré, Hibernate exécute les instructions sql à l'aide de
left outer join
, ce qui peut entraîner dans les résultats retournés par la db. Ce qui les a aidé a été de définir simplement une commande sur le résultat, à l'aide de OrderColumn:À l'aide de Java enterprise contexte dans Wildfly (8.2.0-Final -) (je pense que c'est Hibernate version 4.3.7) la solution pour moi a été, pour la première persistent à l'enfant un les ajouter à la collection lazy:
Géré ce simplement en appelant le vide() la méthode. Pour ce scénario,
parent.getChildren().isEmpty()
avant