2010-04-09 29 views
22

Sto usando Hibernate con annotazioni (in primavera), e ho un oggetto che ha una relazione ordinata, molti-a-uno che un oggetto figlio che ha una chiave primaria composta, un componente di cui è una chiave esterna all'id dell'oggetto genitore.@OneToMany e chiavi primarie composite?

La struttura simile a questa:

+=============+     +================+ 
| ParentObj |     | ObjectChild | 
+-------------+ 1   0..* +----------------+ 
| id (pk)  |-----------------| parentId  | 
| ...   |     | name   | 
+=============+     | pos   | 
           | ...   | 
           +================+ 

Ho provato una varietà di combinazioni di annotazioni, nessuno dei quali sembra funzionare. Questo è il più vicino io sono stato in grado di elaborare con:

@Entity 
public class ParentObject { 
    @Column(nullable=false, updatable=false) 
    @Id @GeneratedValue(generator="...") 
    private String id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER, cascade={CascadeType.ALL}) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    ... 
} 

@Entity 
public class ChildObject { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(nullable=false, updatable=false) 
     private String parentId; 

     @Column(nullable=false, updatable=false) 
     private String name; 

     @Column(nullable=false, updatable=false) 
     private int pos; 

     @Override 
     public String toString() { 
      return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString(); 
     } 

     ... 
    } 

    @EmbeddedId 
    private Pk pk; 

    @ManyToOne 
    @JoinColumn(name="parentId") 
    private ParentObject parent; 

    ... 
} 

sono arrivato a questo dopo un lungo periodo di sperimentazione in cui la maggior parte dei miei altri tentativi ceduta entità che di ibernazione non riusciva nemmeno a caricare per vari motivi .

AGGIORNAMENTO: Grazie a tutti per i commenti; Ho fatto dei progressi. Ho apportato alcune modifiche e penso che sia più vicino (ho aggiornato il codice sopra). Ora, tuttavia, il problema riguarda inserto. L'oggetto genitore sembra salvare bene, ma gli oggetti figli non stanno salvando, e quello che sono stato in grado di determinare è che l'ibernazione non sta compilando la parte parentId della chiave primaria (composita) degli oggetti figli, quindi io ' m ottenendo un errore non-unico:

org.hibernate.NonUniqueObjectException: 
    a different object with the same identifier value was already associated 
    with the session: [org.kpruden.ObjectChild#null.attr1[0]] 

sto popolando le name e pos attributi in mio codice, ma naturalmente io non conoscere l'ID genitore, perché non è stato ancora salvato. Qualche idea su come convincere l'ibernazione a riempirlo?

Grazie!

+2

Buona fortuna. Un progetto su cui mi trovavo doveva abbandonare totalmente le chiavi composite a favore delle chiavi sintetiche a causa del scarso supporto di Hibernate. –

+0

L'eccezione potrebbe essere causata dall'utilizzo dell'elenco <>. È necessario utilizzare Set <> per evitare la duplicazione della chiave utilizzando la definizione hashCode e equals. Hibernate considera due elementi "logicamente identici" in un elenco come due elementi, quindi ha causato l'eccezione. – Popeye

risposta

2

Dopo tanta sperimentazione e frustrazione, alla fine ho deciso che non posso fare esattamente quello che voglio.

In definitiva, sono andato avanti e ho dato all'oggetto figlio la sua chiave sintetica e l'ho lasciato gestire da Hibernate. Non è l'ideale, dal momento che la chiave è grande quasi quanto il resto dei dati, ma funziona.

+0

'@JoinColumns ({ @JoinColumn (name = "userfirstname_fk", referencedColumnName = "firstName"), @ JoinColumn (name = "userlastname_fk", referencedColumnName = "lastName") }) ' – ahaaman

+1

Una buona decisione: provare a fare qualsiasi cosa tranne la roba di base in Hibernate richiede troppa confusione non intuitiva, di solito faccio la stessa cosa - dai TUTTO a primario - mantiene le cose più semplici da comprendere per Hibernate. – jasop

15

Il manuale Manning Java Persistence with Hibernate ha un esempio che spiega come eseguire questa operazione nella Sezione 7.2. Fortunatamente, anche se non possiedi il libro, puoi vedere un esempio del codice sorgente scaricando la versione JPA del progetto di esempio Caveat Emptor (collegamento diretto here) ed esaminando le classi Category e CategorizedItem nel pacchetto auction.model.

Riassumo anche le annotazioni chiave di seguito. Fammi sapere se è ancora un no-go.

ParentObject:

@Entity 
public class ParentObject { 
    @Id @GeneratedValue 
    @Column(name = "parentId", nullable=false, updatable=false) 
    private Long id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    public Long getId() { return id; } 
    public List<ObjectChild> getAttrs() { return attrs; } 
} 

ChildObject:

@Entity 
public class ChildObject { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(name = "parentId", nullable=false, updatable=false) 
     private Long objectId; 

     @Column(nullable=false, updatable=false) 
     private String name; 

     @Column(nullable=false, updatable=false) 
     private int pos; 
     ... 
    } 

    @EmbeddedId 
    private Pk id; 

    @ManyToOne 
    @JoinColumn(name="parentId", insertable = false, updatable = false) 
    @org.hibernate.annotations.ForeignKey(name = "FK_CHILD_OBJECT_PARENTID") 
    private ParentObject parent; 

    public Pk getId() { return id; } 
    public ParentObject getParent() { return parent; } 
} 
+0

Purtroppo non è stato d'aiuto: ho ricevuto lo stesso errore. Gli unici cambiamenti che vedo da ciò che ho sono l'aggiunta dell'attributo @ForeignKey e l'aggiunta di {insertable, updatable} = false sull'attributo padre. C'è qualcos'altro che non vedo? Grazie! –

+0

Ci sono alcuni altri cambiamenti. (1) Prova a cambiare il tipo di dati parentId su Long; (2) Credo che i getter siano necessari; (3) prestare molta attenzione ai nomi delle colonne per ParentObject # id, ParentObject # attrs, ChildObject.Pk # objectId e ChildObject # parent; (4) Il campo id di ParentObject richiede l'annotazione @Id (e @GeneratedValue se non si fornisce manualmente l'id); (5) Ho rinominato ChildObject # pk in ChildObject # id, anche se non penso che questa modifica fosse necessaria. Se hai tutto questo, puoi fornire il codice di lettura che viene lanciato con un contesto circostante? – RTBarnard

+0

Re (2) sopra: getter e setter non sono necessari. Hibernate può costruire proxy per fare ciò che deve fare. Trovo che sia meglio considerare questa "magia". – John

2

primo luogo, nel ParentObject, "fissare" l'attributo mappedBy che dovrebbe essere impostato su "parent". Inoltre (ma questo è forse un errore di battitura) aggiungere un @Id nota:

@Entity 
public class ParentObject { 
    @Id 
    @GeneratedValue 
    private String id; 

    @OneToMany(mappedBy="parent", fetch=FetchType.EAGER) 
    @IndexColumn(name = "pos", base=0) 
    private List<ObjectChild> attrs; 

    // getters/setters 
} 

Poi, in ObjectChild, aggiungere un attributo name alla objectId nella chiave composta:

@Entity 
public class ObjectChild { 
    @Embeddable 
    public static class Pk implements Serializable { 
     @Column(name = "parentId", nullable = false, updatable = false) 
     private String objectId; 

     @Column(nullable = false, updatable = false) 
     private String name; 

     @Column(nullable = false, updatable = false) 
     private int pos; 
    } 

    @EmbeddedId 
    private Pk pk; 

    @ManyToOne 
    @JoinColumn(name = "parentId", insertable = false, updatable = false) 
    private ParentObject parent; 

    // getters/setters 

} 

E anche aggiungere insertable = false, updatable = false a @JoinColumn perché stiamo ripetendo la colonna parentId nella mappatura di questa entità.

Con questi cambiamenti, la persistenza e la lettura delle entità funziona correttamente (testata con Derby).

+0

Ho appena trascorso mezz'ora a cercare di risolvere questo problema e il mio problema era che il mio database precedente aveva la chiave esterna nella tabella come "prevediId" ed è quello che ho copiato nella mia annotazione JPA e per ragioni sconosciute, Spring decise che la colonna doveva quindi essere "forecast_id" nel database e cadde. Deve essere un algoritmo per identificare automaticamente le colonne leggermente errate. Ho modificato '@Column (nome =' a "forecastid" (tutto in minuscolo) e Spring ha deciso di comportarci. – Adam

0

Sembra che tu sia molto vicino e sto cercando di fare la stessa cosa nel mio attuale sistema. Ho iniziato con la chiave surrogata ma vorrei rimuoverla a favore di una chiave primaria composta composta dal PK del genitore e dall'indice nella lista.

sono stato in grado di ottenere una relazione uno-a-uno che condivide la PK dalla tabella master utilizzando un generatore di "straniero":

@Entity 
@GenericGenerator(
    name = "Parent", 
    strategy = "foreign", 
    parameters = { @Parameter(name = "property", value = "parent") } 
) 
public class ChildObject implements Serializable { 
    @Id 
    @GeneratedValue(generator = "Parent") 
    @Column(name = "parent_id") 
    private int parentId; 

    @OneToOne(mappedBy = "childObject") 
    private ParentObject parentObject; 
    ... 
} 

Mi chiedo se si potrebbe aggiungere il @GenericGenerator e @ GeneratedValue per risolvere il problema di Hibernate non assegnando il PK appena acquisito del genitore durante l'inserimento.

0

Dopo aver salvato l'oggetto padre, è necessario impostare in modo esplicito parentId negli oggetti figlio affinché gli inserti sugli oggetti figlio funzionino.

1

Si dovrebbe incorporare il riferimento ParentObject solo in ChildObject.Pk piuttosto che mappa genitore e parentId separatamente:

(getter, setter, Hibernate non attributi relativi a problematiche di accesso e membro parole chiave omessi)

class ChildObject { 
    @Embeddable 
    static class Pk { 
     @ManyToOne... 
     @JoinColumn(name="parentId") 
     ParentObject parent; 

     @Column... 
     String name... 
     ... 
    } 

    @EmbeddedId 
    Pk id; 
} 

In ParentObject quindi metti semplicemente @OneToMany(mappedBy="id.parent") e funziona.

1

Trovata questa domanda cercando la risposta al problema, ma le risposte non risolvono il mio problema, perché stavo cercando lo @OneToMany che non è adatto alla struttura del tavolo che stavo cercando. @ElementCollection è la misura giusta nel mio caso. Uno dei trucchi di esso credo sia che guarda l'intera fila di relazioni come unica, non solo l'id delle righe.

@Entity 
public class ParentObject { 
@Column(nullable=false, updatable=false) 
@Id @GeneratedValue(generator="...") 
private String id; 

@ElementCollection 
@CollectionTable(name = "chidren", joinColumns = @JoinColumn(name = "parent_id")) 
private List<ObjectChild> attrs; 

... 
} 

@Embeddable 
public static class ObjectChild implements Serializable { 
    @Column(nullable=false, updatable=false) 
    private String parentId; 

    @Column(nullable=false, updatable=false) 
    private String name; 

    @Column(nullable=false, updatable=false) 
    private int pos; 

    @Override 
    public String toString() { 
     return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString(); 
    } 

    ... getters and setters REQUIRED (at least they were for me) 
} 
0

Dopo aver trascorso tre giorni in questo, penso di aver trovato una soluzione, ma ad essere onesti, non mi piace e che può sicuramente essere migliorato. Tuttavia, funziona e risolve il nostro problema.

Questo è il tuo costruttore di entità, ma potresti anche farlo nel metodo setter. Inoltre, ho usato un oggetto Collection ma dovrebbe essere identico o simile per lista:

... 
public ParentObject(Collection<ObjectChild> children) { 
    Collection<ObjectChild> occ = new ArrayList<ObjectChild>(); 
    for(ObjectChild obj:children){ 
     obj.setParent(this); 
     occ.add(obj); 
    } 
    this.attrs = occ; 
} 
... 

In sostanza, come qualcun altro ha suggerito, dobbiamo prima impostare manualmente tutte id genitore dei bambini prima di salvare il genitore (insieme a tutti bambini)

Problemi correlati