java - objects - Hibernate-@ElementCollection-Extraño comportamiento de eliminar/insertar
@elementcollection vs @onetomany (2)
@Entity
public class Person {
@ElementCollection
@CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID"))
private List<Location> 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!
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 recopilació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.
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
enEmbeddable
. Sin embargo, para eliminar o actualizar un elemento de la asignaciónElementCollection
, normalmente se requiere una clave única. De lo contrario, en cada actualización, el proveedor de JPA necesitaría eliminar todo deCollectionTable
para laEntity
, y luego volver a insertar los valores. Por lo tanto, es probable que el proveedor de JPA asuma que la combinación de todos los campos deEmbeddable
es única, en combinación con la clave externa (JoinColunm
(s)). Sin embargo, esto podría ser ineficiente o simplemente no factible siEmbeddable
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<Location> 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