Qual è la differenza tra @JoinColumn e mappedBy quando si utilizza un'associazione JPA @OneToMany


516

Qual è la differenza tra:

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
    @JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
    private List<Branch> branches;
    ...
}

e

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}

1
Vedi anche Qual è il lato proprietario in una domanda di mappatura ORM per una spiegazione davvero buona dei problemi coinvolti.
Dirkt

Risposte:


545

L'annotazione @JoinColumnindica che questa entità è il proprietario della relazione (vale a dire: la tabella corrispondente ha una colonna con una chiave esterna alla tabella di riferimento), mentre l'attributo mappedByindica che l'entità in questo lato è l'inverso della relazione, e il proprietario risiede nell'entità "altro". Questo significa anche che puoi accedere all'altra tabella dalla classe che hai annotato con "mappedBy" (relazione completamente bidirezionale).

In particolare, per il codice nella domanda le annotazioni corrette apparirebbero così:

@Entity
public class Company {
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
    private List<Branch> branches;
}

@Entity
public class Branch {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "companyId")
    private Company company;
}

3
in entrambi i casi Branch ha campo con ID azienda.
Mykhaylo Adamovych,

3
La tabella della società non ha una colonna con una chiave esterna per la tabella di riferimento - Branch ha riferimento alla società .. perché stai dicendo "la tabella corrispondente ha una colonna con una chiave esterna per la tabella di riferimento"? Potresti spiegare qualche altro pls.
Mykhaylo Adamovych,

13
@MykhayloAdamovych Ho aggiornato la mia risposta con un codice di esempio. Si noti che è un errore utilizzare @JoinColumninCompany
Óscar López il

10
@MykhayloAdamovych: No, non è proprio vero. Se Branchnon ha una proprietà a cui fa riferimento Company, ma la tabella sottostante ha una colonna che lo fa, è possibile utilizzare @JoinTableper mapparla. Questa è una situazione insolita, perché normalmente si mapperebbe la colonna nell'oggetto che corrisponde alla sua tabella, ma può accadere ed è perfettamente legittima.
Tom Anderson,

4
Questo è un altro motivo per non apprezzare gli ORM. La documentazione è spesso troppo complicata e, nei miei libri, ciò si snoda su un territorio troppo magico. Sono stato alle prese con questo problema e quando seguito parola per parola per un @OneToOne, le righe figlio vengono aggiornate con un nullnella loro colonna FKey che fa riferimento al genitore.
Ashesh,

225

@JoinColumnpotrebbe essere utilizzato da entrambi i lati della relazione. La domanda era su come utilizzare @JoinColumnsul @OneToManylato (caso raro). E il punto qui è nella duplicazione delle informazioni fisiche (nome della colonna) insieme a una query SQL non ottimizzata che produrrà alcune UPDATEdichiarazioni aggiuntive .

Secondo la documentazione :

Poiché molti a uno sono (quasi) sempre il lato proprietario di una relazione bidirezionale nelle specifiche JPA, l'associazione da uno a molti è annotata da@OneToMany(mappedBy=...)

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
} 

Troopha una relazione bidirezionale da una a molte con Soldierla proprietà della truppa. Non è necessario (non è necessario) definire alcuna mappatura fisica nel mappedBylato.

Per mappare uno a molti bidirezionale, con il lato uno-a-molti come lato proprietario , devi rimuovere l' mappedByelemento e impostare i molti su uno @JoinColumncome insertablee updatablesu falso. Questa soluzione non è ottimizzata e produrrà alcune UPDATEdichiarazioni aggiuntive .

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}

1
Non riesco a capire come Troop possa essere il proprietario nel tuo secondo frammento, il Soldato è ancora il proprietario, poiché contiene chiavi esterne che fanno riferimento a Troop. (Sto usando mysql, ho verificato con il tuo approccio).
Akhilesh,

10
Nel tuo esempio l'annotazione si mappedBy="troop"riferisce a quale campo?
Fractaliste,

5
@Fractaliste l'annotazione si mappedBy="troop"riferisce alla truppa di proprietà nella classe Soldato. Nel codice sopra la proprietà non è visibile perché qui Mykhaylo lo ha omesso, ma puoi dedurne l'esistenza dal getter getTroop (). Controlla la risposta di Óscar López , è molto chiaro e otterrai il punto.
nicolimo86,

1
Questo esempio è l'abuso della specifica JPA 2. Se l'obiettivo dell'autore è creare una relazione bidirezionale, dovrebbe utilizzare mappedBy sul lato genitore e JoinColumn (se necessario) sul lato figlio. Con l'approccio presentato qui stiamo ottenendo 2 relazioni unidirezionali: OneToMany e ManyToOne che sono indipendenti ma solo per fortuna (più per uso improprio) queste 2 relazioni sono definite usando la stessa chiave esterna
aurelije,

1
Se stai usando JPA 2.x, la mia risposta qui sotto è un po 'più pulita. Anche se suggerisco di provare entrambi i percorsi e vedere cosa fa Hibernate quando genera le tabelle. Se fai parte di un nuovo progetto, scegli la generazione che ritieni adatta alle tue esigenze. Se sei su un database legacy e non vuoi cambiare la struttura, scegli quale corrisponde al tuo schema.
Snekse,

65

Poiché questa è una domanda molto comune, ho scritto questo articolo , su cui si basa questa risposta.

Associazione one-to-many unidirezionale

Come ho spiegato in questo articolo , se usi l' @OneToManyannotazione con @JoinColumn, allora hai un'associazione unidirezionale, come quella tra l' Postentità genitore e il figlio PostCommentnel diagramma seguente:

Associazione one-to-many unidirezionale

Quando si utilizza un'associazione uno-a-molti unidirezionale, solo la parte padre mappa l'associazione.

In questo esempio, solo l' Postentità definirà @OneToManyun'associazione PostCommentall'entità figlio :

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<PostComment> comments = new ArrayList<>();

Associazione uno-a-molti bidirezionale

Se si utilizza il set @OneToManycon l' mappedByattributo, si dispone di un'associazione bidirezionale. Nel nostro caso, sia l' Postentità ha una raccolta di PostCommententità figlio, sia l' PostCommententità figlio ha un riferimento Postall'entità madre , come illustrato dal diagramma seguente:

Associazione uno-a-molti bidirezionale

Nella PostCommententità, la poststruttura entità viene mappato come segue:

@ManyToOne(fetch = FetchType.LAZY)
private Post post;

Il motivo per cui abbiamo impostato esplicitamente l' fetchattributo FetchType.LAZYè perché, per impostazione predefinita, tutte @ManyToOnee le @OneToOneassociazioni vengono recuperate con impazienza, il che può causare problemi di query N + 1. Per maggiori dettagli su questo argomento, consulta questo articolo .

Nella Postentità, l' commentsassociazione viene mappato come segue:

@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();

L' mappedByattributo @OneToManydell'annotazione fa riferimento alla postproprietà PostCommentnell'entità figlio e, in questo modo, Hibernate sa che l'associazione bidirezionale è controllata dal @ManyToOnelato, che è responsabile della gestione del valore della colonna Chiave esterna su cui si basa questa relazione di tabella.

Per un'associazione bidirezionale, è inoltre necessario disporre di due metodi di utilità, come addChilde removeChild:

public void addComment(PostComment comment) {
    comments.add(comment);
    comment.setPost(this);
}

public void removeComment(PostComment comment) {
    comments.remove(comment);
    comment.setPost(null);
}

Questi due metodi garantiscono che entrambe le parti dell'associazione bidirezionale non siano sincronizzate. Senza sincronizzare entrambe le estremità, Hibernate non garantisce che le modifiche allo stato dell'associazione si propagheranno al database.

Per maggiori dettagli sul miglior wat per sincronizzare le associazioni bidirezionali con JPA e Hibernate, consulta questo articolo .

Quale scegliere?

L' associazione unidirezionale @OneToManynon funziona molto bene , quindi dovresti evitarlo.

Stai meglio usando il bidirezionale @OneToManyche è più efficiente .


32

L'annotazione mappataBy idealmente dovrebbe sempre essere usata nella parte Parent (classe Company) della relazione bidirezionale, in questo caso dovrebbe essere nella classe Company che punta alla variabile membro 'company' della classe Child (classe Branch)

L'annotazione @JoinColumn viene utilizzata per specificare una colonna mappata per unire un'associazione di entità, questa annotazione può essere utilizzata in qualsiasi classe (padre o figlio) ma dovrebbe idealmente essere utilizzata solo in un lato (nella classe padre o nella classe figlio non in entrambi) qui in questo caso l'ho usato nel lato Child (classe Branch) della relazione bidirezionale che indica la chiave esterna nella classe Branch.

di seguito è riportato l'esempio funzionante:

classe genitore, azienda

@Entity
public class Company {


    private int companyId;
    private String companyName;
    private List<Branch> branches;

    @Id
    @GeneratedValue
    @Column(name="COMPANY_ID")
    public int getCompanyId() {
        return companyId;
    }

    public void setCompanyId(int companyId) {
        this.companyId = companyId;
    }

    @Column(name="COMPANY_NAME")
    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="company")
    public List<Branch> getBranches() {
        return branches;
    }

    public void setBranches(List<Branch> branches) {
        this.branches = branches;
    }


}

classe per bambini, ramo

@Entity
public class Branch {

    private int branchId;
    private String branchName;
    private Company company;

    @Id
    @GeneratedValue
    @Column(name="BRANCH_ID")
    public int getBranchId() {
        return branchId;
    }

    public void setBranchId(int branchId) {
        this.branchId = branchId;
    }

    @Column(name="BRANCH_NAME")
    public String getBranchName() {
        return branchName;
    }

    public void setBranchName(String branchName) {
        this.branchName = branchName;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="COMPANY_ID")
    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }


}

20

Vorrei solo aggiungere che @JoinColumnnon sempre deve essere correlato alla posizione delle informazioni fisiche come suggerisce questa risposta. È possibile combinare @JoinColumncon @OneToMany, anche se la tabella padre non ha dati della tabella che punta alla tabella figlio.

Come definire la relazione unidirezionale OneToMany in JPA

Unidirezionale OneToMany, No Inverse ManyToOne, No Join Table

Sembra essere disponibile solo in JPA 2.x+però. È utile per le situazioni in cui si desidera che la classe figlio contenga solo l'ID del genitore, non un riferimento completo.


hai ragione, il supporto per OneToMany unidirezionale senza tabella di join è stato introdotto in JPA2
aurelije,

17

Non sono d'accordo con la risposta accettata qui da Óscar López. Quella risposta è inaccurata!

NON è ciò @JoinColumnche indica che questa entità è il proprietario della relazione. Invece, è l' @ManyToOneannotazione che lo fa (nel suo esempio).

Le annotazioni di relazione come @ManyToOne, @OneToManye @ManyToManyindicano a JPA / Hibernate di creare una mappatura. Per impostazione predefinita, ciò avviene tramite una tabella di join separata.


@JoinColumn

Lo scopo di @JoinColumnè creare una colonna di join se non esiste già. In tal caso, questa annotazione può essere utilizzata per nominare la colonna di join.


mappedBy

Lo scopo del MappedByparametro è di indicare a JPA: NON creare un'altra tabella di join poiché la relazione è già stata mappata dall'entità opposta di questa relazione.



Ricorda: MappedByè una proprietà delle annotazioni della relazione il cui scopo è generare un meccanismo per mettere in relazione due entità che, per impostazione predefinita, creano creando una tabella di join. MappedByinterrompe il processo in una direzione.

Si MappedBydice che l' entità che non utilizza sia il proprietario della relazione perché i meccanismi della mappatura sono dettati all'interno della sua classe attraverso l'uso di una delle tre annotazioni della mappatura rispetto al campo chiave esterna. Ciò non solo specifica la natura del mapping, ma indica anche la creazione di una tabella di join. Inoltre, l'opzione di sopprimere la tabella di join esiste anche applicando l'annotazione @JoinColumn sulla chiave esterna che la mantiene invece nella tabella dell'entità proprietario.

Quindi in sintesi: @JoinColumno crea una nuova colonna di join o rinomina una esistente; mentre il MappedByparametro funziona in modo collaborativo con le annotazioni di relazione dell'altra classe (figlio) al fine di creare un mapping tramite una tabella di join o creando una colonna di chiave esterna nella tabella associata dell'entità proprietario.

Per illustrare come MapppedByfunziona, considera il codice qui sotto. Se il MappedByparametro dovesse essere eliminato, Hibernate creerebbe effettivamente DUE tabelle di join! Perché? Perché c'è una simmetria nelle relazioni molti-a-molti e Hibernate non ha una logica per selezionare una direzione rispetto all'altra.

Quindi usiamo MappedByper dire a Hibernate che abbiamo scelto l'altra entità per dettare la mappatura della relazione tra le due entità.

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    private List<Drivers> drivers;
}

L'aggiunta di @JoinColumn (name = "driverID") nella classe del proprietario (vedi sotto), impedirà la creazione di una tabella di join e, invece, creerà una colonna chiave esterna driverID nella tabella Cars per costruire un mapping:

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    @JoinColumn(name = "driverID")
    private List<Drivers> drivers;
}

1

JPA è un'API a più livelli, i diversi livelli hanno le proprie annotazioni. Il livello più alto è il (1) livello di entità che descrive le classi persistenti, quindi si ha il (2) livello di database relazionale che presuppone che le entità siano mappate su un database relazionale e (3) il modello java.

Livello 1 annotazioni: @Entity, @Id, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany. È possibile introdurre persistenza nella propria applicazione usando solo queste annotazioni di alto livello. Ma poi devi creare il tuo database in base alle ipotesi fatte da JPA. Queste annotazioni specificano il modello entità / relazione.

Livello 2 annotazioni: @Table, @Column, @JoinColumn, ... influenzare la mappatura da entità / oggetti da tabelle di database relazionali / colonne se non sono soddisfatti con le impostazioni predefinite del JPA o se avete bisogno di mappare a un database esistente. Queste annotazioni possono essere viste come annotazioni di implementazione, specificano come deve essere eseguita la mappatura.

A mio avviso, è meglio attenersi il più possibile alle annotazioni di alto livello e quindi introdurre le annotazioni di livello inferiore secondo necessità.

Per rispondere alle domande: il @OneToMany/ mappedByè più bello perché utilizza solo le annotazioni dal dominio dell'entità. Anche @oneToMany/ @JoinColumnva bene ma usa un'annotazione di implementazione in cui ciò non è strettamente necessario.


1

Lasciami rendere semplice.
Puoi utilizzare @JoinColumn su entrambi i lati indipendentemente dalla mappatura.

Dividiamolo in tre casi.
1) Mappatura unidirezionale dalla filiale alla società.
2) Mappatura bidirezionale dalla Società alla Filiale.
3) Solo mappatura unidirezionale dalla Società alla Filiale.

Pertanto, qualsiasi caso d'uso rientrerà in queste tre categorie. Quindi lasciami spiegare come usare @JoinColumn e mappedBy .
1) Mappatura unidirezionale dalla filiale alla società.
Utilizzare JoinColumn nella tabella Branch.
2) Mappatura bidirezionale dalla Società alla Filiale.
Usa mappedBy nella tabella Company come descritto dalla risposta di @Mykhaylo Adamovych.
3) Mappatura unidirezionale dalla Società alla Filiale.
Usa @JoinColumn nella tabella Azienda.

@Entity
public class Company {

@OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
@JoinColumn(name="courseId")
private List<Branch> branches;
...
}

Ciò dice che in base alla mappatura "courseId" della chiave esterna nella tabella dei rami, visualizzami l'elenco di tutti i rami. NOTA: in questo caso non è possibile recuperare la società dalla filiale, esiste solo la mappatura unidirezionale dalla società alla filiale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.