2010-10-26 12 views
16

Ho un problema durante la rimozione di elementi da un elenco mappato come descritto sopra. Ecco la mappatura:Violazione del vincolo in Hibernate OneToMany unidirezionale Mapping con JoinTable e OrderColumn durante la rimozione degli elementi

 
@Entity 
@Table(name = "foo") 
class Foo { 

    private List bars; 

    @OneToMany 
    @OrderColumn(name = "order_index") 
    @JoinTable(name = "foo_bar_map", joinColumns = @JoinColumn(name = "foo_id"), inverseJoinColumns = @JoinColumn(name = "bar_id")) 
    @Fetch(FetchMode.SUBSELECT) 
    public List getBars() { 
     return bars; 
    } 
} 

Inserimento Bar-istanze e salvare il Foo funziona bene, ma quando rimuovo un elemento dall'elenco e salvare di nuovo, il vincolo univoco sulla bar_id nella tabella di mappatura viene violato. Le seguenti dichiarazioni di SQL sono emessi da Hibernate, e questi sembrano piuttosto strano:

 
LOG: execute : delete from foo_bar_map where foo_id=$1 and order_index=$2 
DETAIL: parameters: $1 = '4', $2 = '6' 
LOG: execute S_5: update foo_bar_map set bar_id=$1 where foo_id=$2 and order_index=$3 
DETAIL: parameters: $1 = '88', $2 = '4', $3 = '0' 
ERROR: duplicate key value violates unique constraint "foo_bar_map_bar_id_key" 

L'errore rende perfettamente senso, date le istruzioni generate da Hibernate (ci sono cinque elementi della lista, ho rimuovere il primo e Hibernate cancella la riga di mappatura con l'indice LAST e tenta di aggiornare i restanti, iniziando dal primo).

Cosa c'è di sbagliato nella mappatura sopra?

risposta

14

la mappatura è completamente valida e funziona con EclipseLink come JPA 2.0 implementazione (senza la Fetch annotazioni ovviamente), ma in realtà non riesce con Hibernate.

Ecco il DDL con Hibernate:

create table foo_bar_map (foo_id bigint not null, bar_id bigint not null, order_index integer not null, primary key (foo_id, order_index), unique (bar_id)) 
alter table foo_bar_map add constraint FK14F1CB7FA042E82 foreign key (bar_id) references Bar4022509 
alter table foo_bar_map add constraint FK14F1CB7B6DBCCDC foreign key (foo_id) references Foo4022509 

Quindi diciamo Foo#1 contiene una lista con Bar#1, Bar#2, Bar#3, la tabella unirsi contiene:

foo_id | bar_id | order_index 
    1 |  1 |   1 
    1 |  2 |   2 
    1 |  3 |   3 

Durante la rimozione, dicono che la prima elemento dall'elenco, Hibernate prima delete l'ultima riga (WTF?) dalla tabella di join:

foo_id | bar_id | order_index 
    1 |  1 |   1 
    1 |  2 |   2 

E quindi tenta di update la colonna bar_id nella tabella di join anziché order_index (WTF !?) per riflettere il "nuovo" ordinamento degli elementi nell'elenco. Prima (schematicamente):

foo_id | bar_id | order_index 
    1 |  2 |   1 
    1 |  2 |   2 

dove il passo successivo comporterebbe:

foo_id | bar_id | order_index 
    1 |  2 |   1 
    1 |  3 |   2 

Ovviamente, questo approccio non suona bene e non funziona causa del vincolo unique su bar_id . Più in generale, perché diavolo fa ibernare Hibernate con lo bar_id invece di aggiornare la colonna order_index?

Ritengo che si tratti di un bug Hibernate (segnalato come HHH-5694, vedere HHH-1268 ora).

+1

Grazie, sì, questo è quello che ho scoperto anche dopo un po 'di tempo trascorso nel debugger. Come una correzione sto usando ManyToMany (che abbassa il vincolo di unicità), ma mi sembra brutto. È interessante notare che nessun altro si è imbattuto in questo problema, poiché considererei questo caso di uso comune ... – tbk

+0

@tbh Sì, rendendo l'associazione un numero molti-a-molti eliminerà il vincolo univoco (per consentire "molti "). Ma questa è davvero una soluzione, Hibernate dovrebbe gestire correttamente uno-a-molti. Potresti voler votare per il problema Jira :) –

0

In genere quando si entra in una tabella di join la relazione è ManyToMany not OneToMany. Prova questo

@ManyToMany 
@OrderColumn(name = "order_index") 
@JoinTable(name = "foo_bar_map", joinColumns = @JoinColumn(name = "foo_id"), inverseJoinColumns = @JoinColumn(name = "bar_id")) 
@Fetch(FetchMode.SUBSELECT) 
public List getBars() { 
    return bars; 
} 
+0

Ma dal mio punto di vista del modello di dominio, è solo OneToMany. Sostituire questo con ManyToMany farebbe leva su alcuni vincoli, che chiaramente non è quello che voglio. Voglio che lo schema sia il più sicuro possibile. Il motivo principale per utilizzare JoinTable qui è quello di tenere traccia dell'indice dell'ordine, che non dovrebbe essere noto nella classe Bar. – tbk

+0

Con JPA standard, OneToMany unidirezionale viene mappato utilizzando una tabella di join (anche se Hibernate consente di mappare ciò senza una tabella di join utilizzando un 'JoinColumn', che ora è supportato anche in JPA 2.0). –

1

No Non penso che sia un errore di ibernazione, e come vedrete se cercate questo errore di ibernazione citato da Pascal Thivent è un bug noto dal 2006 e non è mai stato risolto.

Perché?

Causa Penso che il problema sia solo nei vincoli sul tavolo e non in ibernazione.

non capisco il motivo per cui v'è un vincolo univoco sulla bar_id

utilizzando un indice ordine significa la vostra collezione è una lista (e non un set!). E un elenco è una raccolta in cui è possibile specificare l'indice per gli elementi aggiunti (corrisponde a OrderColumn).
La differenza tra un elenco e un set è che voi e potete gli stessi dati due volte (o più), ma gli stessi dati saranno a indici diversi. Quindi si può avere lo stesso bar_id a indici diversi, non è necessario specificare un vincolo univoco su bar_id. E la chiave primaria non può essere (foo_id, order_index) perché l'elenco di pattern autorizza gli stessi dati a diversi indici. Forse il tuo PK dovrebbe essere (foo_id, bar_id, order_index)?

Penso che il problema sia in questo modo :)

+0

Hai mai sentito parlare di un set ordinato? I due vincoli sono ortogonali: un ordine definito e unicità. – tbk

Problemi correlati