2011-10-26 44 views
18

Tengo una pregunta sobre Hibernate 3.6.7 y JPA 2.0.Hibernate inserta duplicados en una colección @OneToMany

Considere siguientes entidades (unos captadores y definidores se omiten por razones de brevedad):

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

ahora esto trozo de código:

// 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()); // -> [[email protected], [email protected]] 

em.getTransaction().commit(); 
em.close(); 

está siendo insertado erróneamente La entidad secundaria dos veces en la lista de hijos de la entidad matriz.

Al hacer uno de los siguientes, el código funciona (no hay entradas duplicadas en la lista) finas:

  • quitar el atributo mappedBy en la entidad matriz
  • realizar alguna operación de lectura en la lista de los niños (por ejemplo, línea de comentario marcada con *)

Esto es obviamente un comportamiento muy extraño. Además, cuando se utiliza EclipseLink como proveedor de persistencia, el código funciona tal como se esperaba (sin duplicados).

¿Es esto un error de Hibernate o me falta algo?

Gracias

+0

¿Es posible añadir el código del método setParent y de los Iguales/métodos hashCode? –

+0

Acabo de agregar los métodos que solicitó. Sin embargo, no creo que este problema esté relacionado con equals/hashCode. – user1014562

+1

Sus métodos iguales no respetan el contrato de Object.equals. Además, el hashCode cambia cuando el ID se genera y se asigna a la entidad. No me sorprendería si el error desapareciera una vez que eliminas hashCode y es igual. Por cierto. Hibernate recomienda no usar ID para implementar equals y hashCode. –

Respuesta

27

Es un error en Hibernate. Sorprendentemente, aún no se informó, feel free to report it.

Las operaciones contra las colecciones diferidas no inicializadas se ponen en cola para ejecutarlas después de la inicialización de la colección, e Hibernate no maneja la situación cuando estas operaciones entran en conflicto con los datos de la base de datos. Por lo general, no es un problema, porque esta cola se borra al flush(), y los posibles cambios conflictivos se propagan a la base de datos al flush() también. Sin embargo, algunos cambios (como la persistencia de entidades con identificadores generados por el generador del tipo IDENTITY, supongo que es su caso) se propagan a la base de datos sin el flush() completo, y en estos casos los conflictos son posibles.

Como solución alternativa se puede flush() la sesión después de que persiste el niño:

em.persist(child); 
em.flush(); 
+1

¡Gracias por su respuesta y la solución! He creado un informe de error y me he referido a su respuesta: [HHH-6776] (https://hibernate.onjira.com/browse/HHH-6776) – user1014562

+0

¿Es realmente extraño que este error no haya sido resuelto todavía? Es tan pobre que es necesario leer los objetos antes de persistir. – nize

+0

También tengo duplicados en oneToMany con una lista. Sin embargo, cambiar a establecer ayuda ¿por qué me veo obligado a utilizar el conjunto cuando quiero usar la lista? El uso de la consulta SQL generada de Hibernate devuelve NO duplicados. Sin embargo, hibernate devuelve duplicados en la lista, incluso la consulta no tiene duplicados devueltos. No se puede encontrar la solución sin usar set. No necesito usar flush, debido a stateless-bean .. – nimo23

1

Mediante el uso de Java en el contexto de la empresa JBoss (8.2.0-Final) (creo que es Hibernate versión 4.3.7) de la solución para mí fue, a persistir en primer lugar el niño una al añadirlo a la colección perezosa:

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

    parent.getChildren().add(cild); 

    parentFacade.edit(parent); 
} 
2

he arreglado este problema diciendo que Hibernate no añadir duplicados en mi colección. En su caso, cambie el tipo de campo children de List<Child> a Set<Child> e implemente equals(Object obj) y hashCode() en la clase Child.

Obviamente, esto no será posible en todos los casos, pero si hay una forma sensata de identificar que una instancia de Child es única, entonces esta solución puede ser relativamente sencilla.

+0

Y estableciendo @OneToMany ((. ..), cascade = CascadeType.MERGE) – Hinotori

2

Me encontré con esta pregunta cuando tuve problemas no con la adición de elementos a una lista anotada con @OneToMany, sino al tratar de iterar sobre los elementos de dicha lista. Los elementos en la lista siempre se duplicaron, a veces mucho más que dos veces. (También sucedió cuando se anotó con @ManyToMany). Usar un conjunto no era una solución aquí, ya que se suponía que estas listas permitían elementos duplicados en ellas.

Ejemplo:

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

Como resultó, Hibernate ejecuta sentencias SQL mediante left outer join, que puede resultar en resultados duplicados devueltos por la db. Lo que ayudó fue simplemente está definiendo un ordenamiento en el resultado, utilizando OrderColumn:

@OrderColumn(name = "columnName")
+0

no puedo agradecerle lo suficiente, ¿me puede indicar un recurso donde pueda leer más sobre esto? –

0

Managed con sólo llamar al método vacío(). Para este escenario,

parent.getChildren().isEmpty()

antes

parent.getChildren().add(child); 
Cuestiones relacionadas