hibernate - mvc - spring framework pdf español
Hibernate inserta duplicados en una colección @OneToMany (5)
Al usar el contexto empresarial de Java en Wildfly (8.2.0-Final) (creo que es la versión 4.3.7 de Hibernate), la solución para mí fue, para persistir primero en el niño y agregarlo 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);
}
Tengo una pregunta sobre Hibernate 3.6.7 y JPA 2.0.
Considere las siguientes entidades (algunos getters y setters se omiten por 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 considera este pedazo 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()); // -> [Child@1, Child@1]
em.getTransaction().commit();
em.close();
La entidad secundaria se inserta erróneamente dos veces en la lista de elementos secundarios de la entidad principal.
Al hacer uno de los siguientes, el código funciona bien (no hay entradas duplicadas en la lista):
- eliminar el atributo
mappedBy
en la entidad principal - realizar alguna operación de lectura en la lista de niños (p. ej., línea de comentario marcada con
*
)
Esto es obviamente un comportamiento muy extraño. Además, cuando se usa EclipseLink como proveedor de persistencia, el código funciona como se esperaba (no hay duplicados).
¿Es este un error de hibernación o me estoy perdiendo algo?
Gracias
Encontré esta pregunta cuando tuve problemas no con la adición de elementos a una lista anotada con @OneToMany, sino al intentar iterar sobre los elementos de dicha lista. Los elementos de la lista siempre se duplicaron, a veces mucho más de dos veces. (También sucedió cuando se anota con @ManyToMany). El uso de un Conjunto no era una solución aquí, ya que estas listas debían permitir elementos duplicados en ellas.
Ejemplo:
@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
@Cascade(CascadeType.ALL)
@LazyCollection(LazyCollectionOption.FALSE)
private List entities;
Al final resultó que, Hibernate ejecuta sentencias de SQL utilizando left outer join
, lo que puede resultar en resultados duplicados devueltos por la db. Lo que ayudó fue simplemente definir un orden en el resultado, utilizando OrderColumn:
@OrderColumn(name = "columnName")
Es un error en Hibernate. Sorprendentemente, no se ha reportado todavía, siéntase libre de reportarlo .
Las operaciones contra colecciones perezosas no inicializadas se ponen en cola para ejecutarse después de que se inicializa la colección, y 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 en conflicto se propagan a la base de datos al flush()
también. Sin embargo, algunos cambios (como la persistencia de entidades con identificadores generados por un tipo de IDENTITY
de tipo, supongo que es su caso) se propagan a la base de datos sin la flush()
completa flush()
, y en estos casos los conflictos son posibles.
Como solución alternativa, puede flush()
la sesión después de persistir al hijo:
em.persist(child);
em.flush();
Maneje esto simplemente llamando al método empty (). Para este escenario,
parent.getChildren().isEmpty()
antes de
parent.getChildren().add(child);
Solucioné este problema diciéndole a Hibernate que no agregara duplicados en mi colección. En su caso, cambie el tipo de campo de sus 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, esta solución puede ser relativamente indolora.