Hibernate insère des doublons dans une collection @OneToMany

j'ai une question concernant L'hibernation 3.6.7 et le 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;
    }
}

maintenant, considérez 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.

quand vous faites l'une des opérations suivantes, le code fonctionne très bien (pas d'entrées en double dans la liste):

  • supprimer le mappedBy l'attribut dans l'entité mère
  • effectuer quelques opérations de lecture sur la liste des enfants (par exemple, ligne de découplage marquée par *)

c'est évidemment un comportement très bizarre. En outre, lorsque vous utilisez EclipseLink comme fournisseur de persistance, le code fonctionne exactement comme prévu (pas de doublons).

Est-ce un insecte D'hibernation ou est-ce que je manque quelque chose?

Merci

19
demandé sur jeha 2011-10-26 18:04:56

5 réponses

c'est un insecte en hibernation. Étonnamment, il n'est pas signalée encore, n'hésitez pas à le signaler.

les opérations contre les collections paresseuses non initialisées sont mises en file d'attente afin de les exécuter après que la collecte ait été initialisée, et Hibernate ne gère pas la situation lorsque ces opérations entrent en conflit avec les données de la base de données. Habituellement, ce n'est pas un problème, parce que cette file d'attente est dégagée sur flush(), et d'éventuelles modifications conflictuelles sont propagées dans la base de données flush(). Cependant, certains changements (comme la persistance d'entités avec des ID générés par générateur de type IDENTITY je suppose que c'est votre cas) sont propagées à la base de données sans la pleine flush(), et dans ces cas des conflits sont possibles.

comme solution de contournement, vous pouvez flush() la session après avoir persisté l'enfant:

em.persist(child); 
em.flush();
27
répondu axtavt 2016-10-02 20:24:40

j'ai corrigé ce problème en disant à Hibernate de ne pas ajouter de doublons dans ma collection. Dans votre cas, changez le type de votre children champ de la List<Child>Set<Child> et de mettre en œuvre equals(Object obj) et hashCode() sur le Child 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 exemple est unique, alors cette solution peut être relativement indolore.

2
répondu carbontax 2015-01-22 15:11:10

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 étaient toujours dupliqués, parfois plus de deux fois. (Cela s'est aussi produit quand @ManyToMany a été annoté). L'utilisation d'un ensemble n'était pas une solution ici, puisque ces listes étaient censées permettre de dupliquer des éléments.

Exemple:

@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
@Cascade(CascadeType.ALL)
@LazyCollection(LazyCollectionOption.FALSE)
private List entities;

comme il s'est avéré, L'hibernation exécute déclarations sql utilisant left outer join, ce qui peut donner des résultats dupliqués retournés par le db. Ce qui a aidé était simplement de définir un ordre sur le résultat, en utilisant OrderColumn:

@OrderColumn(name = "columnName")
2
répondu xor_eq 2016-11-22 13:58:34

en utilisant Java enterprise context dans Wildfly (8.2.0-Final) (je pense que c'est Hibernate version 4.3.7) la solution pour moi a été, d'abord persister l'enfant et l'ajouter à la collection paresseuse:

...
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void test(){
    Child child = new Child();
    child.setParent(parent);
    childFacade.create(child);

    parent.getChildren().add(cild);

    parentFacade.edit(parent);
}
1
répondu McIntosh 2015-01-15 15:43:45

a géré cela en appelant simplement la méthode empty (). Pour ce scénario,

parent.getChildren().isEmpty()

avant

parent.getChildren().add(child);
-1
répondu lakshitha 2017-01-13 10:32:24