Was ist der Unterschied zwischen @JoinColumn und mappedBy bei Verwendung einer JPA @ OneToMany-Zuordnung?

516

Was ist der Unterschied zwischen:

@Entity
public class Company {

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

und

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}
Mykhaylo Adamovych
quelle
1
Eine wirklich gute Erklärung der damit verbundenen Probleme finden Sie unter Was ist die Besitzerseite in einer ORM-Zuordnungsfrage?
Dirkt

Antworten:

545

Die Anmerkung @JoinColumngibt an, dass diese Entität der Eigentümer der Beziehung ist (dh die entsprechende Tabelle hat eine Spalte mit einem Fremdschlüssel für die referenzierte Tabelle), während das Attribut mappedByangibt, dass die Entität auf dieser Seite die Umkehrung der Beziehung ist, und Der Eigentümer wohnt in der "anderen" Einheit. Dies bedeutet auch, dass Sie von der Klasse, die Sie mit "mappedBy" (vollständig bidirektionale Beziehung) kommentiert haben, auf die andere Tabelle zugreifen können.

Insbesondere für den Code in der Frage würden die korrekten Anmerkungen folgendermaßen aussehen:

@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;
}
Óscar López
quelle
3
In beiden Fällen hat die Niederlassung ein Feld mit der Firmen-ID.
Mykhaylo Adamovych
3
Die Firmentabelle hat keine Spalte mit einem Fremdschlüssel für die referenzierte Tabelle - Branch hat sich auf Company bezogen. Warum sagen Sie "Die entsprechende Tabelle hat eine Spalte mit einem Fremdschlüssel für die referenzierte Tabelle"? Könnten Sie noch einige pls erklären.
Mykhaylo Adamovych
13
@MykhayloAdamovych Ich habe meine Antwort mit Beispielcode aktualisiert. Beachten Sie, dass es ein Fehler ist, @JoinColumninCompany
Óscar López
10
@MykhayloAdamovych: Nein, das ist eigentlich nicht ganz richtig. Wenn Branchkeine Eigenschaft vorhanden ist, auf die verwiesen Companywird, die zugrunde liegende Tabelle jedoch eine entsprechende Spalte enthält, können Sie @JoinTablesie zuordnen. Dies ist eine ungewöhnliche Situation, da Sie normalerweise die Spalte in dem Objekt zuordnen würden, das der Tabelle entspricht. Dies kann jedoch vorkommen und ist absolut legitim.
Tom Anderson
4
Dies ist ein weiterer Grund, ORMs nicht zu mögen. Die Dokumentation ist oft zu zwielichtig, und in meinen Büchern schlängelt sich dies auf zu viel magischem Gebiet. Ich habe mit diesem Problem zu kämpfen, und wenn Wort für Wort für a gefolgt wird @OneToOne, werden die nulluntergeordneten Zeilen mit einem in ihrer FKey-Spalte aktualisiert , das auf das übergeordnete Element verweist.
Ashesh
225

@JoinColumnkönnte auf beiden Seiten der Beziehung verwendet werden. Die Frage war über die Verwendung @JoinColumnauf der @OneToManySeite (seltener Fall). Der Punkt hier ist die Duplizierung physischer Informationen (Spaltenname) zusammen mit einer nicht optimierten SQL-Abfrage, die einige zusätzliche UPDATEAnweisungen erzeugt .

Laut Dokumentation :

Da viele zu eins (fast) immer die Eigentümerseite einer bidirektionalen Beziehung in der JPA-Spezifikation sind, wird die Eins-zu-Viele-Zuordnung von kommentiert@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() {
    ...
} 

Troophat eine bidirektionale Beziehung zu einer Soldierdurch viele durch das Truppenvermögen. Sie müssen (dürfen) keine physische Zuordnung in der mappedBySeite definieren.

Um eine bidirektionale Eins -zu-Viele-Seite mit der Eins-zu-Viele-Seite als Besitzerseite zuzuordnen , müssen Sie das mappedByElement entfernen und die Viele auf Eins @JoinColumnals insertableund updatableauf Falsch setzen. Diese Lösung ist nicht optimiert und führt zu einigen zusätzlichen UPDATEAnweisungen.

@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() {
    ...
}
Mykhaylo Adamovych
quelle
1
Ich kann nicht herausfinden, wie Troop in Ihrem zweiten Snippet Eigentümer sein kann. Soldier ist immer noch der Eigentümer, da es Fremdschlüssel enthält, die auf Troop verweisen. (Ich benutze MySQL, ich habe mit Ihrem Ansatz überprüft).
Akhilesh
10
In Ihrem Beispiel mappedBy="troop"bezieht sich die Anmerkung auf welches Feld?
Fractaliste
5
@Fractaliste Die Anmerkung mappedBy="troop"bezieht sich auf die Eigenschaftstruppe in der Klasse Soldat. Im obigen Code ist die Eigenschaft nicht sichtbar, da Mykhaylo sie hier weggelassen hat, aber Sie können ihre Existenz durch den Getter getTroop () ableiten. Überprüfen Sie die Antwort von Óscar López , es ist sehr klar und Sie werden den Punkt bekommen.
nicolimo86
1
Dieses Beispiel ist ein Missbrauch der JPA 2-Spezifikation. Wenn das Ziel des Autors darin besteht, eine bidirektionale Beziehung zu erstellen, sollte mappedBy auf der übergeordneten Seite und JoinColumn (falls erforderlich) auf der untergeordneten Seite verwendet werden. Mit dem hier vorgestellten Ansatz erhalten wir zwei unidirektionale Beziehungen: OneToMany und ManyToOne, die unabhängig sind, aber nur durch Glück (mehr durch Missbrauch) werden diese
beiden
1
Wenn Sie JPA 2.x verwenden, ist meine Antwort unten etwas sauberer. Ich schlage jedoch vor, beide Routen auszuprobieren und zu sehen, was Hibernate beim Generieren der Tabellen tut. Wenn Sie an einem neuen Projekt arbeiten, wählen Sie die Generation aus, die Ihrer Meinung nach Ihren Anforderungen entspricht. Wenn Sie sich in einer Legacy-Datenbank befinden und die Struktur nicht ändern möchten, wählen Sie diejenige aus, die Ihrem Schema entspricht.
Snekse
65

Da dies eine sehr häufige Frage ist, habe ich diesen Artikel geschrieben , auf dem diese Antwort basiert.

Unidirektionale Eins-zu-Viele-Assoziation

Wie ich in diesem Artikel erklärt habe , haben Sie, wenn Sie die @OneToManyAnnotation mit verwenden @JoinColumn, eine unidirektionale Zuordnung, wie die zwischen der übergeordneten PostEntität und dem untergeordneten Element PostCommentin der folgenden Abbildung:

Unidirektionale Eins-zu-Viele-Assoziation

Bei Verwendung einer unidirektionalen Eins-zu-Viele-Zuordnung ordnet nur die übergeordnete Seite die Zuordnung zu.

In diesem Beispiel Postdefiniert nur die Entität eine @OneToManyZuordnung zur untergeordneten PostCommentEntität:

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

Bidirektionale Eins-zu-Viele-Assoziation

Wenn Sie das @OneToManymit dem mappedByAttributsatz verwenden, haben Sie eine bidirektionale Zuordnung. In unserem Fall verfügt sowohl die PostEntität über eine Sammlung von PostCommentuntergeordneten Entitäten als auch die untergeordnete PostCommentEntität über einen Verweis auf die übergeordnete PostEntität, wie in der folgenden Abbildung dargestellt:

Bidirektionale Eins-zu-Viele-Assoziation

In der PostCommentEntität wird die postEntitätseigenschaft wie folgt zugeordnet:

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

Der Grund, warum wir das fetchAttribut explizit festlegen , FetchType.LAZYist, dass standardmäßig alle @ManyToOneund @OneToOneZuordnungen eifrig abgerufen werden, was zu N + 1-Abfrageproblemen führen kann. Weitere Informationen zu diesem Thema finden Sie in diesem Artikel .

In der PostEntität wird die commentsZuordnung wie folgt zugeordnet:

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

Das mappedByAttribut der @OneToManyAnnotation verweist auf die postEigenschaft in der untergeordneten PostCommentEntität. Auf diese Weise weiß Hibernate, dass die bidirektionale Zuordnung von der @ManyToOneSeite gesteuert wird , die für die Verwaltung des Spaltenwerts für den Fremdschlüssel verantwortlich ist, auf dem diese Tabellenbeziehung basiert.

Für eine bidirektionale Zuordnung benötigen Sie außerdem zwei Dienstprogrammmethoden wie addChildund removeChild:

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

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

Diese beiden Methoden stellen sicher, dass beide Seiten der bidirektionalen Zuordnung nicht synchron sind. Ohne beide Enden zu synchronisieren, garantiert Hibernate nicht, dass Änderungen des Zuordnungsstatus in die Datenbank übertragen werden.

Weitere Informationen zum besten Wat zum Synchronisieren bidirektionaler Zuordnungen mit JPA und Hibernate finden Sie in diesem Artikel .

Welches soll ich wählen?

Die unidirektionale @OneToManyZuordnung funktioniert nicht sehr gut , daher sollten Sie sie vermeiden.

Sie sind besser dran, wenn Sie das bidirektionale verwenden, @OneToManydas effizienter ist .

Vlad Mihalcea
quelle
32

Die Annotation mappedBy sollte idealerweise immer auf der übergeordneten Seite (Company-Klasse) der bidirektionalen Beziehung verwendet werden. In diesem Fall sollte sie in der Company-Klasse sein und auf die Mitgliedsvariable 'company' der Child-Klasse (Branch-Klasse) verweisen.

Die Annotation @JoinColumn wird verwendet, um eine zugeordnete Spalte für den Beitritt zu einer Entitätszuordnung anzugeben. Diese Annotation kann in jeder Klasse (Parent oder Child) verwendet werden, sollte jedoch idealerweise nur auf einer Seite verwendet werden (entweder in der Parent-Klasse oder nicht in der Child-Klasse) in beiden) hier in diesem Fall habe ich es auf der Child-Seite (Branch-Klasse) der bidirektionalen Beziehung verwendet, die den Fremdschlüssel in der Branch-Klasse angibt.

Unten ist das Arbeitsbeispiel:

Elternklasse, Firma

@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;
    }


}

Kinderklasse, Zweig

@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;
    }


}
Koralle
quelle
20

Ich möchte nur hinzufügen, dass @JoinColumndies nicht immer mit dem physischen Informationsort zusammenhängen muss, wie diese Antwort nahelegt. Sie können kombinieren @JoinColumnmit , @OneToManyauch wenn die übergeordnete Tabelle keine Tabellendaten zeigt auf der untergeordneten Tabelle hat.

So definieren Sie eine unidirektionale OneToMany-Beziehung in JPA

Unidirektional OneToMany, No Inverse ManyToOne, No Join Table

Es scheint jedoch nur in verfügbar zu sein JPA 2.x+. Dies ist nützlich in Situationen, in denen die untergeordnete Klasse nur die ID des übergeordneten Elements enthalten soll und keine vollständige Referenz.

Snekse
quelle
Sie haben Recht, die Unterstützung für unidirektionale OneToMany ohne Join-Tabelle wird in JPA2 eingeführt
aurelije
17

Ich bin mit der hier akzeptierten Antwort von Óscar López nicht einverstanden. Diese Antwort ist ungenau!

Es ist NICHT, @JoinColumnwas darauf hinweist, dass diese Entität der Eigentümer der Beziehung ist. Stattdessen ist es die @ManyToOneAnnotation, die dies tut (in seinem Beispiel).

Die Beziehung Anmerkungen wie @ManyToOne, @OneToManyund @ManyToManysagen , JPA / Hibernate eine Zuordnung zu erstellen. Standardmäßig erfolgt dies über eine separate Join-Tabelle.


@ JoinColumn

Der Zweck von @JoinColumnbesteht darin, eine Verknüpfungsspalte zu erstellen , falls noch keine vorhanden ist. Wenn dies der Fall ist, kann diese Anmerkung verwendet werden, um die Verknüpfungsspalte zu benennen .


MappedBy

Der Zweck des MappedByParameters besteht darin, JPA anzuweisen: Erstellen Sie KEINE weitere Verknüpfungstabelle, da die Beziehung bereits von der entgegengesetzten Entität dieser Beziehung zugeordnet wird.



Denken Sie daran: MappedByist eine Eigenschaft der Beziehungsanmerkungen, deren Zweck darin besteht, einen Mechanismus zum Verknüpfen von zwei Entitäten zu generieren, der standardmäßig durch Erstellen einer Verknüpfungstabelle erstellt wird. MappedBystoppt diesen Prozess in eine Richtung.

Die nicht verwendete Entität MappedBywird als Eigentümer der Beziehung bezeichnet, da die Mechanik der Zuordnung innerhalb ihrer Klasse durch die Verwendung einer der drei Zuordnungsanmerkungen für das Fremdschlüsselfeld bestimmt wird. Dies gibt nicht nur die Art der Zuordnung an, sondern weist auch die Erstellung einer Verknüpfungstabelle an. Darüber hinaus besteht die Option zum Unterdrücken der Join-Tabelle auch durch Anwenden der Annotation @JoinColumn auf den Fremdschlüssel, wodurch dieser stattdessen in der Tabelle der Eigentümerentität verbleibt.

Zusammenfassend @JoinColumnlässt sich sagen, dass entweder eine neue Join-Spalte erstellt oder eine vorhandene umbenannt wird. Während der MappedByParameter mit den Beziehungsanmerkungen der anderen (untergeordneten) Klasse zusammenarbeitet, um eine Zuordnung entweder über eine Verknüpfungstabelle oder durch Erstellen einer Fremdschlüsselspalte in der zugeordneten Tabelle der Eigentümerentität zu erstellen.

Beachten Sie MapppedByden folgenden Code, um die Funktionsweise zu veranschaulichen . Wenn MappedByParameter gelöscht würden, würde Hibernate tatsächlich ZWEI Join-Tabellen erstellen! Warum? Weil es eine Symmetrie in vielen-zu-vielen-Beziehungen gibt und der Ruhezustand keine Gründe hat, eine Richtung über die andere zu wählen.

Wir verwenden daher MappedBy, um Hibernate mitzuteilen, dass wir die andere Entität ausgewählt haben, um die Zuordnung der Beziehung zwischen den beiden Entitäten zu diktieren.

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

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

Durch Hinzufügen von @JoinColumn (name = "driverID") in der Eigentümerklasse (siehe unten) wird die Erstellung einer Join-Tabelle verhindert. Erstellen Sie stattdessen eine driverID-Fremdschlüsselspalte in der Cars-Tabelle, um eine Zuordnung zu erstellen:

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

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

JPA ist eine geschichtete API, die verschiedenen Ebenen haben ihre eigenen Anmerkungen. Die höchste Ebene ist die (1) Entitätsebene, die persistente Klassen beschreibt. Dann haben Sie die (2) relationale Datenbankebene, die davon ausgeht, dass die Entitäten einer relationalen Datenbank zugeordnet sind, und (3) das Java-Modell.

Stufe 1 Anmerkungen: @Entity, @Id, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany. Sie können Ihrer Anwendung Persistenz verleihen, indem Sie nur diese Anmerkungen auf hoher Ebene verwenden. Aber dann müssen Sie Ihre Datenbank gemäß den von JPA getroffenen Annahmen erstellen. Diese Anmerkungen geben das Entitäts- / Beziehungsmodell an.

Stufe 2 Anmerkungen: @Table, @Column, @JoinColumn, ... Einfluss die Zuordnung von Einheiten / Eigenschaften zu den relationalen Datenbanktabellen / Spalten , wenn Sie nicht mit PPV-Vorgaben erfüllt sind oder wenn Sie in eine bestehende Datenbank abzubilden. Diese Anmerkungen können als Implementierungsanmerkungen angesehen werden. Sie geben an, wie die Zuordnung erfolgen soll.

Meiner Meinung nach ist es am besten, sich so weit wie möglich an die Anmerkungen auf hoher Ebene zu halten und dann die Anmerkungen auf niedrigerer Ebene nach Bedarf einzuführen.

Um die Fragen zu beantworten: Das @OneToMany/ mappedByist am schönsten, da es nur die Anmerkungen aus der Entitätsdomäne verwendet. Das @oneToMany/ @JoinColumnist ebenfalls in Ordnung, verwendet jedoch eine Implementierungsanmerkung, wenn dies nicht unbedingt erforderlich ist.

Bruno Ranschaert
quelle
1

Lassen Sie es mich einfach machen.
Sie können @JoinColumn unabhängig von der Zuordnung auf beiden Seiten verwenden.

Teilen wir dies in drei Fälle ein.
1) Unidirektionale Zuordnung von der Niederlassung zur Firma.
2) Bidirektionale Zuordnung von Unternehmen zu Niederlassung.
3) Nur unidirektionale Zuordnung von Unternehmen zu Niederlassung.

Jeder Anwendungsfall fällt also unter diese drei Kategorien. Lassen Sie mich erklären, wie Sie @JoinColumn und mappedBy verwenden .
1) Unidirektionale Zuordnung von der Niederlassung zur Firma.
Verwenden Sie JoinColumn in der Branch-Tabelle.
2) Bidirektionale Zuordnung von Unternehmen zu Niederlassung.
Verwenden Sie mappedBy in der Firmentabelle, wie in der Antwort von @Mykhaylo Adamovych beschrieben.
3) Unidirektionale Zuordnung von Unternehmen zu Niederlassung.
Verwenden Sie einfach @JoinColumn in der Firmentabelle .

@Entity
public class Company {

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

Dies besagt, dass ich auf der Grundlage der Fremdschlüsselzuordnung "courseId" in der Verzweigungstabelle eine Liste aller Zweige abrufen kann. HINWEIS: In diesem Fall können Sie keine Firma von der Niederlassung abrufen. Es gibt nur eine unidirektionale Zuordnung von Firma zu Niederlassung.

Vinjamuri Satya Krishna
quelle