Hibernate – @ElementCollection – Strange delete / insert behavior

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

Data la seguente struttura di class, quando provo ad aggiungere una nuova posizione all’elenco delle posizioni della persona, risulta sempre nelle seguenti query SQL:

 DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson 

E

 A lotsa' inserts into the PERSON_LOCATIONS table 

Hibernate (3.5.x / JPA 2) cancella tutti i record associati per la Persona specificata e reinserisce tutti i record precedenti, più il nuovo.

Ho avuto l’idea che il metodo equals / hashcode su Location avrebbe risolto il problema, ma non ha cambiato nulla.

Ogni suggerimento è apprezzato!

Il problema è spiegato in qualche modo nella pagina su ElementCollection del wikibook JPA:

Chiavi primarie in CollectionTable

Le specifiche JPA 2.0 non forniscono un modo per definire l’ Id in Embeddable . Tuttavia, per eliminare o aggiornare un elemento del mapping di ElementCollection , è normalmente richiesta una chiave univoca. In caso contrario, ad ogni aggiornamento il provider JPA dovrà eliminare tutto dalla CollectionTable per l’ Entity e quindi reinserire i valori. Pertanto, il fornitore JPA presumibilmente supporrà che la combinazione di tutti i campi in Embeddable sia unica, in combinazione con la chiave esterna ( JoinColunm (s)). Questo tuttavia potrebbe essere inefficiente o semplicemente non fattibile se l’ Embeddable è big o complesso.

E questo è esattamente (la parte in grassetto) cosa succede qui (Hibernate non genera una chiave primaria per la tabella di raccolta e non ha modo di rilevare quale elemento della collezione è cambiato e cancellerà il vecchio contenuto dalla tabella per inserire il nuovo contenuto).

Tuttavia, se si definisce un @OrderColumn (per specificare una colonna utilizzata per mantenere l’ordine persistente di un elenco – che avrebbe senso dal momento che si sta utilizzando un List ), Hibernate creerà una chiave primaria (composta dalla colonna ordine e join column ) e sarà in grado di aggiornare la tabella di raccolta senza eliminare l’intero contenuto.

Qualcosa di simile (se si desidera utilizzare il nome della colonna predefinito):

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

Riferimenti

  • Specifica JPA 2.0
    • Sezione 11.1.12 “Annotazione di ElementCollection”
    • Sezione 11.1.39 “OrderColumn Annotation”
  • JPA Wikibook
    • Java Persistence / ElementCollection

Oltre alla risposta di Pascal, devi anche impostare almeno una colonna come 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; } } 

Questo requisito è documentato in AbstractPersistentCollection :

Soluzione alternativa per situazioni come HHH-7072. Se l’elemento collection è un componente costituito interamente da proprietà nullable, al momento è necessario ricreare in modo forzato l’intera raccolta. Vedi l’uso di hasNotNullableColumns nel costruttore AbstractCollectionPersister per maggiori informazioni. Per eliminare riga per riga, sarebbe necessario SQL come “WHERE (COL =? OR (COL è null AND? È null))”, piuttosto che l’attuale “WHERE COL =?” (fallisce per null per la maggior parte dei DB). Si noti che il param dovrebbe essere associato due volte. Fino a quando alla fine non avremo aggiunto i concetti di “bind dei parametri” all’AST in ORM 5+, la gestione di questo tipo di condizioni è estremamente difficile o imansible. Forzare la ricreazione non è l’ideale, ma in realtà non c’è altra opzione in ORM 4.