Czy ktoś może wyjaśnić mappedBy w JPA i Hibernate?

175

Jestem nowy w hibernacji i muszę używać relacji jeden do wielu i wiele do jednego. Jest to relacja dwukierunkowa w moich obiektach, dzięki czemu mogę przechodzić z dowolnego kierunku. mappedByjest zalecanym sposobem, aby się do tego zabrać, jednak nie mogłem tego zrozumieć. Czy ktoś może wyjaśnić:

  • jaki jest zalecany sposób korzystania z niego?
  • jaki cel to rozwiązuje?

Ze względu na mój przykład, oto moje zajęcia z adnotacjami:

  • Airline POSIADA wielu AirlineFlights
  • Wielu AirlineFlights należy do JEDNEGO Airline

Linia lotnicza :

@Entity 
@Table(name="Airline")
public class Airline {
    private Integer idAirline;
    private String name;

    private String code;

    private String aliasName;
    private Set<AirlineFlight> airlineFlights = new HashSet<AirlineFlight>(0);

    public Airline(){}

    public Airline(String name, String code, String aliasName, Set<AirlineFlight> flights) {
        setName(name);
        setCode(code);
        setAliasName(aliasName);
        setAirlineFlights(flights);
    }

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="IDAIRLINE", nullable=false)
    public Integer getIdAirline() {
        return idAirline;
    }

    private void setIdAirline(Integer idAirline) {
        this.idAirline = idAirline;
    }

    @Column(name="NAME", nullable=false)
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = DAOUtil.convertToDBString(name);
    }

    @Column(name="CODE", nullable=false, length=3)
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = DAOUtil.convertToDBString(code);
    }

    @Column(name="ALIAS", nullable=true)
    public String getAliasName() {
        return aliasName;
    }
    public void setAliasName(String aliasName) {
        if(aliasName != null)
            this.aliasName = DAOUtil.convertToDBString(aliasName);
    }

    @OneToMany(fetch=FetchType.LAZY, cascade = {CascadeType.ALL})
    @JoinColumn(name="IDAIRLINE")
    public Set<AirlineFlight> getAirlineFlights() {
        return airlineFlights;
    }

    public void setAirlineFlights(Set<AirlineFlight> flights) {
        this.airlineFlights = flights;
    }
}

Linie lotnicze:

@Entity
@Table(name="AirlineFlight")
public class AirlineFlight {
    private Integer idAirlineFlight;
    private Airline airline;
    private String flightNumber;

    public AirlineFlight(){}

    public AirlineFlight(Airline airline, String flightNumber) {
        setAirline(airline);
        setFlightNumber(flightNumber);
    }

    @Id
    @GeneratedValue(generator="identity")
    @GenericGenerator(name="identity", strategy="identity")
    @Column(name="IDAIRLINEFLIGHT", nullable=false)
    public Integer getIdAirlineFlight() {
        return idAirlineFlight;
    }
    private void setIdAirlineFlight(Integer idAirlineFlight) {
        this.idAirlineFlight = idAirlineFlight;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="IDAIRLINE", nullable=false)
    public Airline getAirline() {
        return airline;
    }
    public void setAirline(Airline airline) {
        this.airline = airline;
    }

    @Column(name="FLIGHTNUMBER", nullable=false)
    public String getFlightNumber() {
        return flightNumber;
    }
    public void setFlightNumber(String flightNumber) {
        this.flightNumber = DAOUtil.convertToDBString(flightNumber);
    }
}

EDYTOWAĆ:

Schemat bazy danych:

AirlineFlights ma idAirline jako ForeignKey, a Airline nie ma idAirlineFlights. To sprawia, że ​​AirlineFlights jest właścicielem / podmiotem identyfikującym?

Teoretycznie chciałbym, aby właścicielem linii lotniczych były linie lotnicze.

wprowadź opis obrazu tutaj

brainydexter
źródło

Odpowiedzi:

151

Określając @JoinColumnw obu modelach, nie masz dwukierunkowej relacji. Masz dwie jednokierunkowe relacje i przy tym bardzo mylące odwzorowanie. Mówisz obu modelom, że „są właścicielami” kolumny IDAIRLINE. Naprawdę tylko jeden z nich powinien! „Normalną” rzeczą jest @JoinColumncałkowite usunięcie z @OneToManyboku i zamiast tego dodanie mappedBy do @OneToMany.

@OneToMany(cascade = CascadeType.ALL, mappedBy="airline")
public Set<AirlineFlight> getAirlineFlights() {
    return airlineFlights;
}

To mówi Hibernate „Idź przejrzyj obiekt fasoli o nazwie„ linia lotnicza ”na rzecz, której mam kolekcję, aby znaleźć konfigurację”.

Affe
źródło
2
Jestem trochę zdezorientowany twoim końcowym opisem mappedBy. Czy ma znaczenie, jak rzeczy są zorganizowane w db? @DB: AirlineFlights ma idAirline jako klucz obcy. Linie lotnicze mają po prostu idAirline jako klucz podstawowy i nie przechowują informacji o AirlineFlights @ DB.
brainydexter
10
Tak, to ma znaczenie. Nazwa w mappedBy mówi Hibernate, gdzie znaleźć konfigurację JoinColumn. (W metodzie getAirline () funkcji AirlineFlight). Sposób, w jaki ją odwzorowałeś, umieszczając kolumnę JoinColumn na linii lotniczej, informujesz linię lotniczą, że jest ona odpowiedzialna za utrzymywanie wartości w drugiej tabeli. Jednostce można powiedzieć, że "posiada" kolumnę w innej tabeli i jest odpowiedzialna za jej aktualizację. Nie jest to coś, co zwykle chcesz robić i może powodować problemy z kolejnością wykonywania instrukcji SQL.
Affe
Zobacz edycję. Na poziomie bazy danych tabela airlineFlight posiada idAirline jako kolumnę klucza obcego. W związku z tym JoinColumn powinien być umieszczony w klasie / tabeli linii lotniczych w odpowiednim ManytoOne, skoro jest właścicielem tej kolumny?
brainydexter
Tak, poleciłbym to zrobić w ten sposób. Jest to najmniej skomplikowana opcja i wydaje się, że nie potrzebujesz niczego więcej.
Affe
„całkowicie zdejmij @JoinColumn ze strony @OneToMany” masz na myśli @ManyToOnestronę, prawda?
nbro
284

MappedBy sygnalizuje hibernację, że klucz do relacji znajduje się po drugiej stronie.

Oznacza to, że chociaż łączysz ze sobą 2 tabele, tylko jedna z nich ma ograniczenie związane z kluczem obcym do drugiej. MappedBy pozwala nadal łączyć się z tabelą niezawierającą ograniczenia do innej tabeli.

Kurt Du Bois
źródło
3
czy możesz wyjaśnić trochę więcej?
Alexander Suraphel,
1
@Kurt Du Bois, dlaczego miałbyś używać mappedByzamiast definiować dwukierunkowy (z ograniczeniami obcego klucza po każdej stronie)?
Kevin Meredith
6
Czasami po prostu nie ma sensu wkładać klucza po obu stronach. Załóżmy na przykład, że masz firmę i przenośny. Przenośny będzie należał tylko do jednej firmy, ale firma będzie miała wiele urządzeń przenośnych.
Kurt Du Bois
Przepraszam redaktora za wycofanie mojej odpowiedzi, ale tak naprawdę w Twojej edycji nie było żadnej wartości dodanej. Ostatnie zdanie nawet nie miało sensu.
Kurt Du Bois
@KurtDuBois tak zmapowane przez przychodzi tylko do obrazu, jak tworzyć bazę danych, tj. Albo używasz mappedby, albo nie hibernuj po stronie javy, zachowuje się podobnie.
22

mappedbymówi sam za siebie, mówi hibernacji, aby nie mapować tego pola. jest już zmapowane przez to pole [nazwa = "pole"].
pole znajduje się w drugiej jednostce (name of the variable in the class not the table in the database).

Jeśli tego nie zrobisz, hibernacja zmapuje te dwie relacje, ponieważ nie jest to ta sama relacja

więc musimy powiedzieć hibernacji, aby wykonała mapowanie tylko z jednej strony i skoordynować między nimi.

Charif DZ
źródło
is mappedBy jest opcjonalne? Ponieważ bez użycia mappedBy otrzymuję ten sam wynik, tj. Dwukierunkowe mapowanie obiektów
Freelancer
nie możesz używać on2many i many2one bez użycia mappedBy w jednej z tych stron to samo dla many2many musisz użyć mappedBy z jednej strony
Charif DZ
Dzięki za wskazanie, co oznacza wartość atrybutu, czyli nazwa pola w drugiej tabeli.
Gab 是 好人
2
Może hibernacja nie zawsze mówi sama za siebie, ale kiedy już to robi, przynajmniej używa interpunkcji
Amalgovinus
1
Dla mnie to nie mówi samo za siebie; przeciwnie, jest to bardzo zagmatwane. Wystarczy spojrzeć na liczbę pytań dotyczących tego, co właściwie jest mappedByi inversedBy. Inne ORMs używać dużo bardziej inteligentny belongsToMany, hasManyatrybuty.
Jan Bodnar
12

mappedby = "obiekt podmiotu tej samej klasy utworzony w innej klasie"

Uwaga: -Mapowane przez może być używane tylko w jednej klasie, ponieważ jedna tabela musi zawierać ograniczenie klucza obcego. jeśli mapowane przez można zastosować po obu stronach, to usuwa klucz obcy z obu tabel, a bez klucza obcego nie ma relacji między dwiema tabelami.

Uwaga: - można go używać do następujących adnotacji: - 1. @ OneTone 2. @ OneToMany 3. @ ManyToMany

Uwaga --- Nie można jej używać do następujących adnotacji: - 1. @ ManyToOne

W jeden do jednego: - Wykonuj po dowolnej stronie mapowania, ale wykonuj tylko po jednej stronie. Spowoduje to usunięcie dodatkowej kolumny ograniczenia klucza obcego w tabeli, do której jest stosowane.

Np. Jeśli zastosujemy mapowanie by w klasie Employee na obiekcie pracownika, to klucz obcy z tabeli Employee zostanie usunięty.

ks gujjar
źródło
12

Relacja między tabelami a relacja encji

W systemie relacyjnej bazy danych relacja między one-to-manytabelami wygląda następująco:

Relacja tabeli <code> jeden do wielu </code>

Zauważ, że relacja jest oparta na kolumnie Foreign Key (np. post_id) W tabeli podrzędnej.

Tak więc istnieje jedno źródło prawdy, jeśli chodzi o zarządzanie one-to-manyrelacjami między stołami.

Teraz, jeśli weźmiesz dwukierunkową relację encji, która mapuje na one-to-manyrelację między tabelami, którą widzieliśmy wcześniej:

Dwukierunkowe powiązanie encji <code> jeden do wielu </code>

Jeśli spojrzysz na powyższy diagram, zobaczysz, że istnieją dwa sposoby zarządzania tą relacją.

W Postjednostce masz commentskolekcję:

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

A w PostCommentThe poststowarzyszenie jest odwzorowany w sposób następujący:

@ManyToOne(
    fetch = FetchType.LAZY
)
@JoinColumn(name = "post_id")
private Post post;

Ponieważ istnieją dwa sposoby przedstawiania kolumny klucza obcego, należy określić, który jest źródłem prawdy, jeśli chodzi o tłumaczenie zmiany stanu skojarzenia na odpowiadającą jej modyfikację wartości kolumny klucza obcego.

MappedBy

mappedByAtrybut informuje, że @ManyToOnestrona jest odpowiedzialna za zarządzanie kolumny klucz obcy, a zbiór jest używany tylko do pobierania jednostek podrzędnych i kaskady zmian stanu podmiotem dominującym dla dzieci (na przykład usuwając rodzic powinien także usunąć podmioty podrzędne).

Zsynchronizuj obie strony skojarzenia dwukierunkowego

Teraz, nawet jeśli zdefiniowałeś mappedByatrybut i @ManyToOnepowiązanie po stronie podrzędnej zarządza kolumną klucza obcego, nadal musisz zsynchronizować obie strony skojarzenia dwukierunkowego.

Najlepszym sposobem na to jest dodanie dwóch metod narzędziowych:

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

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

addCommentI removeCommentmetody zapewnienia, że obie strony są zsynchronizowane. Tak więc, jeśli dodamy jednostkę podrzędną, jednostka podrzędna musi wskazywać na jednostkę nadrzędną, a jednostka nadrzędna powinna zawierać element podrzędny w kolekcji podrzędnej.

Aby uzyskać więcej informacji na temat najlepszego sposobu synchronizacji wszystkich dwukierunkowych typów skojarzeń encji, zapoznaj się z tym artykułem .

Vlad Mihalcea
źródło
0

Zacząłeś od mapowania ManyToOne, a następnie umieściłeś mapowanie OneToMany również w trybie dwukierunkowym. Następnie po stronie OneToMany (zwykle twoja tabela / klasa nadrzędna), musisz wspomnieć o „mappedBy” (mapowanie jest wykonywane przez iw tabeli / klasie potomnej), więc hibernacja nie utworzy tabeli mapowania EXTRA w DB (jak TableName = parent_child).

Akshay N. Shelke
źródło