tutorial mvc mediante libro framework español desarrollo books arquitectura aplicaciones hibernate jpa-2.0 one-to-many

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.