Hibernate – @ElementCollection – Extraño comportamiento de eliminar / insertar

@Entity public class Person { @ElementCollection @CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID")) private List locations; [...] } @Embeddable public class Location { [...] } 

Dada la siguiente estructura de clase, cuando trato de agregar una nueva ubicación a la lista de Ubicaciones de persona, siempre da como resultado las siguientes consultas SQL:

 DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson 

Y

 A lotsa' inserts into the PERSON_LOCATIONS table 

Hibernate (3.5.x / JPA 2) borra todos los registros asociados para la persona dada y vuelve a insertar todos los registros anteriores, más el nuevo.

Tenía la idea de que el método equals / hashcode en Location resolvería el problema, pero no cambió nada.

Cualquier sugerencia es apreciada!

El problema se explica de alguna manera en la página sobre ElementCollection del wikibook de JPA:

Claves principales en CollectionTable

La especificación JPA 2.0 no proporciona una manera de definir el Id en Embeddable . Sin embargo, para eliminar o actualizar un elemento de la asignación ElementCollection , normalmente se requiere una clave única. De lo contrario, en cada actualización, el proveedor de JPA necesitaría eliminar todo de CollectionTable para la Entity , y luego volver a insertar los valores. Por lo tanto, es probable que el proveedor de JPA asum que la combinación de todos los campos de Embeddable es única, en combinación con la clave externa ( JoinColunm (s)). Sin embargo, esto podría ser ineficiente o simplemente no factible si Embeddable es grande o compleja.

Y esto es exactamente (la parte en negrita) lo que sucede aquí (Hibernate no genera una clave primaria para la tabla de colección y no tiene manera de detectar qué elemento de la colección cambió y eliminará el contenido anterior de la tabla para insertar el nuevo contenido).

Sin embargo, si define @OrderColumn (para especificar una columna utilizada para mantener el orden persistente de una lista, lo que tendría sentido ya que está utilizando una List ), Hibernate creará una clave principal (hecha de la columna de orden y la columna de unión ) y podrá actualizar la tabla de colección sin eliminar todo el contenido.

Algo como esto (si quiere usar el nombre de columna predeterminado):

 @Entity public class Person { ... @ElementCollection @CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID")) @OrderColumn private List locations; ... } 

Referencias

  • Especificación JPA 2.0
    • Sección 11.1.12 “Anotación de ElementCollection”
    • Sección 11.1.39 “Anotación de OrderColumn”
  • JPA Wikilibro
    • Java Persistence / ElementCollection

Además de la respuesta de Pascal, también debe configurar al menos una columna como NOT NULL :

 @Embeddable public class Location { @Column(name = "path", nullable = false) private String path; @Column(name = "parent", nullable = false) private String parent; public Location() { } public Location(String path, String parent) { this.path = path; this.parent= parent; } public String getPath() { return path; } public String getParent() { return parent; } } 

Este requisito está documentado en AbstractPersistentCollection :

Solución para situaciones como HHH-7072. Si el elemento de recostackción es un componente que consta exclusivamente de propiedades que admiten nulos, actualmente tenemos que recrear a la fuerza toda la colección. Consulte el uso de hasNotNullableColumns en el constructor AbstractCollectionPersister para obtener más información. Para eliminar fila por fila, eso requeriría SQL como “¿DÓNDE (COL =? O (COL es nulo AND? Es nulo))”, en lugar de “WHERE COL =?” Actual. (falla para nulo para la mayoría de los DB) Tenga en cuenta que el param tendría que estar limitado dos veces. Hasta que eventualmente agreguemos conceptos de “puntos de unión de parámetros” al AST en ORM 5+, manejar este tipo de condición es extremadamente difícil o imposible. Forzar la recreación no es ideal, pero realmente no hay otra opción en ORM 4.