Jaka jest różnica między @JoinColumn a mappedBy przy użyciu skojarzenia JPA @OneToMany

516

Jaka jest różnica pomiędzy:

@Entity
public class Company {

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

i

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}
Mychajło Adamowicz
źródło
1
Zobacz także, po której stronie właściciela jest pytanie dotyczące mapowania ORM, aby uzyskać naprawdę dobre wyjaśnienie związanych z tym problemów.
dirkt

Odpowiedzi:

545

Adnotacja @JoinColumnwskazuje, że ta jednostka jest właścicielem relacji (to znaczy: odpowiednia tabela ma kolumnę z kluczem obcym do tabeli, do której istnieje odwołanie), podczas gdy atrybut mappedBywskazuje, że jednostka po tej stronie jest odwrotnością relacji, oraz właściciel mieszka w „innym” podmiocie. Oznacza to również, że możesz uzyskać dostęp do drugiej tabeli z klasy, do której dodałeś adnotację „mappedBy” (w pełni dwukierunkowa relacja).

W szczególności dla kodu w pytaniu prawidłowe adnotacje wyglądałyby następująco:

@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
źródło
3
w obu przypadkach oddział ma pole z identyfikatorem firmy.
Mychajło Adamowicz w dniu
3
Tabela firmowa nie ma kolumny z kluczem obcym do tabeli, do której istnieje odwołanie - oddział ma odniesienie do firmy ... dlaczego mówisz „odpowiednia tabela ma kolumnę z kluczem obcym do tabeli, do której istnieje odniesienie” Czy mógłbyś wyjaśnić więcej informacji?
Mychajło Adamowicz w dniu
13
@MykhayloAdamovych Zaktualizowałem odpowiedź o przykładowy kod. Zauważ, że błędem jest użycie @JoinColumnwCompany
Óscar López
10
@MykhayloAdamovych: Nie, to nie do końca prawda. Jeśli Branchnie ma właściwości, do której się odwołuje Company, ale tabela bazowa ma taką kolumnę, możesz użyć jej @JoinTabledo odwzorowania. Jest to niezwykła sytuacja, ponieważ normalnie można odwzorować kolumnę w obiekcie, która odpowiada jej tabeli, ale może się zdarzyć i jest całkowicie uzasadniona.
Tom Anderson,
4
To kolejny powód, by nie lubić ORM. Dokumentacja jest często zbyt podejrzana, a w moich książkach meandruje ona na zbyt wielkim magicznym terytorium. Zmagałem się z tym problemem, a kiedy śledziłem słowo po słowie @OneToOne, wiersze podrzędne są aktualizowane za pomocą nullw kolumnie FKey, która odwołuje się do elementu nadrzędnego.
Ashesh,
225

@JoinColumnmoże być stosowany po obu stronach relacji. Pytanie dotyczyło używania @JoinColumnz @OneToManyboku (rzadki przypadek). Chodzi tutaj o fizyczne powielanie informacji (nazwa kolumny) wraz z niezoptymalizowanym zapytaniem SQL, które wygeneruje dodatkowe UPDATEinstrukcje .

Według dokumentacji :

Ponieważ wiele do jednego jest (prawie) zawsze właścicielem strony relacji dwukierunkowej w specyfikacji JPA, powiązanie typu jeden do wielu jest opatrzone adnotacją@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() {
    ...
} 

Troopma dwukierunkową relację jeden do wielu Soldierdzięki własności wojska. Nie musisz (nie wolno) definiować żadnego fizycznego mapowania z mappedByboku.

Aby zmapować dwukierunkowy jeden na wiele, ze stroną jeden do wielu jako właścicielem , musisz usunąć mappedByelement i ustawić wiele na jeden @JoinColumnjako insertablei updatablena fałsz. To rozwiązanie nie jest zoptymalizowane i spowoduje wygenerowanie dodatkowych UPDATEinstrukcji.

@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() {
    ...
}
Mychajło Adamowicz
źródło
1
Nie jestem w stanie dowiedzieć się, w jaki sposób Oddział może być właścicielem w drugim fragmencie, Żołnierz nadal jest właścicielem, ponieważ zawiera klucz obcy odnoszący się do Oddziału. (Używam mysql, sprawdziłem z twoim podejściem).
Akhilesh
10
W twoim przykładzie adnotacja mappedBy="troop"odnosi się do którego pola?
Fractaliste
5
@ Fractaliste adnotacja mappedBy="troop"odnosi się do żołnierza własności w klasie Żołnierz. W powyższym kodzie właściwość nie jest widoczna, ponieważ tutaj Mykhaylo ją pominął, ale można wywnioskować jej istnienie przez getter getTroop (). Sprawdź odpowiedź Óscara Lópeza , jest bardzo jasna i dostaniesz sens.
nicolimo86,
1
Ten przykład stanowi nadużycie specyfikacji JPA 2. Jeśli celem autora jest utworzenie relacji dwukierunkowej, należy użyć mappedBy po stronie nadrzędnej i JoinColumn (w razie potrzeby) po stronie podrzędnej. Dzięki przedstawionemu tutaj podejściu otrzymujemy 2 jednokierunkowe relacje: OneToMany i ManyToOne, które są niezależne, ale tylko przez szczęście (więcej przez niewłaściwe użycie) te 2 relacje są zdefiniowane przy użyciu tego samego klucza obcego
aurelije
1
Jeśli używasz JPA 2.x, moja odpowiedź poniżej jest trochę czystsza. Chociaż sugeruję wypróbowanie obu tras i sprawdzenie, co robi Hibernacja, gdy generuje tabele. Jeśli masz nowy projekt, wybierz dowolną generację, która Twoim zdaniem odpowiada Twoim potrzebom. Jeśli korzystasz ze starszej bazy danych i nie chcesz zmieniać struktury, wybierz dowolną, która pasuje do Twojego schematu.
Snekse
65

Ponieważ jest to bardzo częste pytanie, napisałem ten artykuł , na którym opiera się ta odpowiedź.

Jednokierunkowe połączenie jeden do wielu

Jak wyjaśniłem w tym artykule , jeśli używasz @OneToManyadnotacji z @JoinColumn, to masz jednokierunkowe powiązanie, takie jak to między Postjednostką nadrzędną a dzieckiem PostCommentna poniższym diagramie:

Jednokierunkowe połączenie jeden do wielu

Podczas korzystania z jednokierunkowego powiązania jeden do wielu, tylko strona nadrzędna mapuje powiązanie.

W tym przykładzie tylko Postjednostka zdefiniuje @OneToManypowiązanie z PostCommentjednostką podrzędną :

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

Dwukierunkowe powiązanie jeden do wielu

Jeśli używasz @OneToManyz mappedByustawionym atrybutem, masz dwukierunkowy stowarzyszenie. W naszym przypadku zarówno Postjednostka ma kolekcję PostCommentjednostek potomnych, jak i PostCommentjednostka potomna ma odwołanie do Postjednostki macierzystej , jak pokazano na poniższym diagramie:

Dwukierunkowe powiązanie jeden do wielu

W PostCommentjednostki, postwłasność jednostka jest odwzorowany w sposób następujący:

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

Powodem, dla którego jawnie ustawiliśmy ten fetchatrybut, FetchType.LAZYjest to, że domyślnie wszystkie @ManyToOnei @OneToOneskojarzenia są pobierane z niecierpliwością, co może powodować problemy z zapytaniami N + 1. Więcej informacji na ten temat można znaleźć w tym artykule .

W Postencji commentspowiązanie jest mapowane w następujący sposób:

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

mappedByAtrybut z @OneToManyodniesieniami adnotacji postwłasności w dziecięcej PostCommentpodmiotu i, w ten sposób, Hibernate wie, że stowarzyszenie dwukierunkowa jest kontrolowany przez @ManyToOnestronę, która jest odpowiedzialna za zarządzanie wartość kolumny klucz obcy tabela ta relacja jest oparta na.

W przypadku powiązania dwukierunkowego potrzebne są również dwie metody narzędziowe, takie jak addChildi removeChild:

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

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

Te dwie metody zapewniają, że obie strony skojarzenia dwukierunkowego nie są zsynchronizowane. Bez synchronizacji obu końców Hibernacja nie gwarantuje, że zmiany stanu skojarzenia zostaną przeniesione do bazy danych.

Aby uzyskać więcej informacji na temat najlepszego sposobu synchronizowania skojarzeń dwukierunkowych z JPA i Hibernacją, zapoznaj się z tym artykułem .

Który wybrać?

Jednokierunkowy @OneToManystowarzyszenie nie działa bardzo dobrze , więc należy go unikać.

Lepiej jest użyć dwukierunkowego, @OneToManyktóry jest bardziej wydajny .

Vlad Mihalcea
źródło
32

Adnotacja zmapowana idealnie powinna być zawsze używana po stronie nadrzędnej (klasa firmy) relacji dwukierunkowej, w tym przypadku powinna znajdować się w klasie firmy, wskazując na zmienną składową „firma” klasy podrzędnej (klasa oddziału)

Adnotacja @JoinColumn służy do określenia odwzorowanej kolumny do dołączenia do powiązania encji, adnotacja ta może być używana w dowolnej klasie (nadrzędnej lub podrzędnej), ale idealnie powinna być używana tylko z jednej strony (w klasie nadrzędnej lub w klasie podrzędnej nie w obu) tutaj w tym przypadku użyłem go po stronie podrzędnej (klasa rozgałęzienia) relacji dwukierunkowej wskazującej klucz obcy w klasie rozgałęzienia.

poniżej znajduje się działający przykład:

klasa rodzicielska, 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;
    }


}

klasa dziecięca, oddział

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


}
Koral
źródło
20

Chciałbym tylko dodać, że @JoinColumnnie zawsze musi to być związane z fizyczną lokalizacją informacji, jak sugeruje ta odpowiedź. Można łączyć @JoinColumnz, @OneToManynawet jeśli tabela nadrzędna nie ma danych tabeli wskazujących na tabelę podrzędną.

Jak zdefiniować jednokierunkową relację OneToMany w JPA

Jednokierunkowy OneToMany, bez odwrotnego ManyToOne, bez tabeli łączenia

Wydaje się, że jest dostępny tylko w JPA 2.x+. Jest to przydatne w sytuacjach, w których chcesz, aby klasa potomna zawierała tylko identyfikator rodzica, a nie pełny w referencji.

Snekse
źródło
masz rację, wsparcie dla jednokierunkowego OneToMany bez tabeli łączenia zostało wprowadzone w JPA2
aurelije
17

Nie zgadzam się z przyjętą tutaj odpowiedzią Óscara Lópeza. Ta odpowiedź jest niedokładna!

NIE @JoinColumnwskazuje, że ten byt jest właścicielem relacji. Zamiast tego robi to @ManyToOneadnotacja (w jego przykładzie).

Adnotacje relacji, takie jak @ManyToOne, @OneToManyi @ManyToManypowiedz JPA / Hibernate, aby utworzyły mapowanie. Domyślnie odbywa się to poprzez osobną tabelę łączenia.


@JoinColumn

Celem @JoinColumnjest utworzenie kolumny łączenia, jeśli jeszcze nie istnieje. Jeśli tak, to można użyć tej adnotacji do nazwania kolumny łączenia.


MappedBy

Celem tego MappedByparametru jest instruowanie JPA: NIE twórz kolejnej tabeli łączenia, ponieważ relacja jest już odwzorowywana przez przeciwny byt tej relacji.



Pamiętaj: MappedByjest właściwością adnotacji relacji, których celem jest wygenerowanie mechanizmu powiązania dwóch jednostek, które domyślnie robią to, tworząc tabelę łączenia. MappedByzatrzymuje ten proces w jednym kierunku.

MappedByMówi się, że nieużywany byt jest właścicielem relacji, ponieważ mechanika odwzorowania jest podyktowana w jego klasie poprzez użycie jednej z trzech adnotacji odwzorowania względem pola klucza obcego. To nie tylko określa charakter odwzorowania, ale także instruuje tworzenie tabeli łączenia. Co więcej, istnieje możliwość wyłączenia tabeli łączenia poprzez zastosowanie adnotacji @JoinColumn nad kluczem obcym, który zamiast tego trzyma go w tabeli encji właściciela.

Podsumowując: @JoinColumnalbo tworzy nową kolumnę łączenia, albo zmienia nazwę istniejącej; podczas gdy MappedByparametr współpracuje z adnotacjami relacji drugiej (podrzędnej) klasy w celu utworzenia odwzorowania poprzez tabelę łączenia lub przez utworzenie kolumny klucza obcego w powiązanej tabeli encji właściciela.

Aby zilustrować sposób MapppedBydziałania, rozważ poniższy kod. Gdyby MappedByparametr został usunięty, Hibernacja utworzyłaby DWIE tabele łączenia! Dlaczego? Ponieważ istnieje symetria w relacjach wiele do wielu, a Hibernacja nie ma uzasadnienia dla wybierania jednego kierunku nad drugim.

Dlatego używamy, MappedByaby powiedzieć Hibernacji, wybraliśmy inny byt, aby dyktować mapowanie relacji między dwoma bytami.

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

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

Dodanie @JoinColumn (name = "driverID") w klasie właściciela (patrz poniżej), uniemożliwi utworzenie tabeli łączenia, a zamiast tego, utworzy kolumnę klucza obcego driverID w tabeli Cars, aby zbudować mapowanie:

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

@Entity
public class Cars {
    @ManyToMany
    @JoinColumn(name = "driverID")
    private List<Drivers> drivers;
}
IqbalHamid
źródło
1

JPA jest warstwowym API, różne poziomy mają własne adnotacje. Najwyższym poziomem jest (1) poziom jednostki, który opisuje trwałe klasy, wtedy masz (2) poziom relacyjnej bazy danych, który zakłada, że ​​jednostki są odwzorowane na relacyjną bazę danych i (3) model Java.

Poziom 1 adnotacje: @Entity, @Id, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany. Możesz wprowadzić trwałość w swojej aplikacji, korzystając tylko z tych adnotacji wysokiego poziomu. Ale musisz utworzyć bazę danych zgodnie z założeniami JPA. Te adnotacje określają model encji / relacji.

Poziom 2 adnotacje: @Table, @Column, @JoinColumn, ... wpływać na mapowanie od jednostek / właściwości do relacyjnych baz danych, tabel / kolumn, jeśli nie jesteś zadowolony z domyślnych JPA lub jeśli trzeba mapować do istniejącej bazy danych. Adnotacje te mogą być postrzegane jako adnotacje dotyczące implementacji, określają sposób mapowania.

Moim zdaniem najlepiej przykleić jak najwięcej do adnotacji na wysokim poziomie, a następnie wprowadzić adnotacje na niższym poziomie w razie potrzeby.

Aby odpowiedzieć na pytania: @OneToMany/ mappedByjest najładniejszy, ponieważ używa tylko adnotacji z domeny encji. Opcja @oneToMany/ @JoinColumnjest również w porządku, ale używa adnotacji implementacyjnej, gdy nie jest to absolutnie konieczne.

Bruno Ranschaert
źródło
1

Pozwól mi to uprościć.
Możesz użyć @JoinColumn po obu stronach, niezależnie od mapowania.

Podzielmy to na trzy przypadki.
1) Jednokierunkowe mapowanie z oddziału do firmy.
2) Dwukierunkowe mapowanie od firmy do oddziału.
3) Tylko jednokierunkowe mapowanie od firmy do oddziału.

Zatem każdy przypadek użycia będzie należał do tych trzech kategorii. Pozwól, że wyjaśnię, jak używać @JoinColumn i mappedBy .
1) Jednokierunkowe mapowanie z oddziału do firmy.
Użyj JoinColumn w tabeli oddziałów.
2) Dwukierunkowe mapowanie od firmy do oddziału.
Użyj mappedBy w tabeli firmy, jak opisano w odpowiedzi @Mykhaylo Adamovych.
3) Jednokierunkowe mapowanie od firmy do oddziału.
Wystarczy użyć @JoinColumn w tabeli firmowej .

@Entity
public class Company {

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

Mówi to, że w oparciu o mapowanie klucza obcego „courseId” w tabeli gałęzi, otrzymam listę wszystkich gałęzi. UWAGA: w tym przypadku nie można pobrać firmy z oddziału, istnieje tylko jednokierunkowe odwzorowanie z firmy na oddział.

Vinjamuri Satya Krishna
źródło