Was ist die „umgekehrte Seite der Assoziation“ in einer bidirektionalen JPA OneToMany / ManyToOne-Assoziation?

167

Im Beispielabschnitt der @OneToManyJPA-Anmerkungsreferenz :

Beispiel 1-59 @OneToMany - Kundenklasse mit Generika

@Entity
public class Customer implements Serializable {
    ...
    @OneToMany(cascade=ALL, mappedBy="customer")
    public Set<Order> getOrders() { 
        return orders; 
    }
    ...
}

Beispiel 1-60 @ManyToOne - Bestellklasse mit Generika

@Entity
public class Order implements Serializable {
    ...
    @ManyToOne
    @JoinColumn(name="CUST_ID", nullable=false)
    public Customer getCustomer() { 
        return customer; 
    }
    ...
}

Es scheint mir, dass das CustomerUnternehmen der Eigentümer des Vereins ist. In der Erläuterung des mappedByAttributs im selben Dokument heißt es jedoch:

Wenn die Beziehung bidirektional ist, setzen Sie das Element mappedBy auf der inversen (nicht besitzenden) Seite der Zuordnung auf den Namen des Felds oder der Eigenschaft, der die Beziehung gehört, wie in Beispiel 1-60 gezeigt.

Wenn ich mich jedoch nicht irre, sieht es so aus, als ob im Beispiel das mappedBytatsächlich auf der Besitzerseite der Assoziation und nicht auf der Nichtbesitzerseite angegeben ist.

Meine Frage lautet also im Grunde:

  1. Welche der Entitäten ist in einer bidirektionalen (Eins-zu-Viele / Viele-zu-Eins) Vereinigung der Eigentümer? Wie können wir die Eine Seite als Eigentümer bestimmen? Wie können wir die Many-Seite als Eigentümer bestimmen?

  2. Was ist mit "der umgekehrten Seite der Assoziation" gemeint? Wie können wir die Eine Seite als die Umkehrung bezeichnen? Wie können wir die Viele-Seite als die Umkehrung bezeichnen?

Behrang Saeedzadeh
quelle
1
Der von Ihnen angegebene Link ist veraltet. Bitte aktualisieren.
MartinL

Antworten:

306

Um dies zu verstehen, müssen Sie einen Schritt zurücktreten. In OO besitzt der Kunde die Bestellungen (Bestellungen sind eine Liste im Kundenobjekt). Ohne Kunden kann es keine Bestellung geben. Der Kunde scheint also der Eigentümer der Bestellungen zu sein.

In der SQL-Welt enthält ein Element jedoch tatsächlich einen Zeiger auf das andere. Da es für N Bestellungen 1 Kunden gibt, enthält jede Bestellung einen Fremdschlüssel für den Kunden, zu dem sie gehört. Dies ist die "Verbindung" und dies bedeutet, dass die Bestellung die Verbindung (Informationen) "besitzt" (oder buchstäblich enthält). Dies ist genau das Gegenteil von der OO / Modellwelt.

Dies kann helfen zu verstehen:

public class Customer {
     // This field doesn't exist in the database
     // It is simulated with a SQL query
     // "OO speak": Customer owns the orders
     private List<Order> orders;
}

public class Order {
     // This field actually exists in the DB
     // In a purely OO model, we could omit it
     // "DB speak": Order contains a foreign key to customer
     private Customer customer;
}

Die umgekehrte Seite ist der OO "Eigentümer" des Objekts, in diesem Fall der Kunde. Der Kunde hat keine Spalten in der Tabelle, in denen die Bestellungen gespeichert werden können. Sie müssen daher angeben, wo in der Auftragstabelle diese Daten gespeichert werden können (was über geschieht mappedBy).

Ein weiteres häufiges Beispiel sind Bäume mit Knoten, die sowohl Eltern als auch Kinder sein können. In diesem Fall werden die beiden Felder in einer Klasse verwendet:

public class Node {
    // Again, this is managed by Hibernate.
    // There is no matching column in the database.
    @OneToMany(cascade = CascadeType.ALL) // mappedBy is only necessary when there are two fields with the type "Node"
    private List<Node> children;

    // This field exists in the database.
    // For the OO model, it's not really necessary and in fact
    // some XML implementations omit it to save memory.
    // Of course, that limits your options to navigate the tree.
    @ManyToOne
    private Node parent;
}

Dies erklärt für die "Fremdschlüssel" viele-zu-eins-Entwurfsarbeiten. Es gibt einen zweiten Ansatz, bei dem eine andere Tabelle verwendet wird, um die Beziehungen aufrechtzuerhalten. In unserem ersten Beispiel haben Sie also drei Tabellen: die mit Kunden, die mit Bestellungen und eine zweispaltige Tabelle mit Primärschlüsselpaaren (customerPK, orderPK).

Dieser Ansatz ist flexibler als der oben beschriebene (er kann problemlos eins zu eins, viele zu eins, eins zu viele und sogar viele zu viele verarbeiten). Der Preis ist das

  • Es ist etwas langsamer (wenn Sie eine andere Tabelle und Verknüpfungen pflegen müssen, werden drei Tabellen anstelle von nur zwei verwendet).
  • Die Join-Syntax ist komplexer (was mühsam sein kann, wenn Sie viele Abfragen manuell schreiben müssen, z. B. wenn Sie versuchen, etwas zu debuggen).
  • Es ist fehleranfälliger, weil Sie plötzlich zu viele oder zu wenige Ergebnisse erhalten können, wenn im Code, der die Verbindungstabelle verwaltet, ein Fehler auftritt.

Deshalb empfehle ich diesen Ansatz selten.

Aaron Digulla
quelle
36
Nur um zu verdeutlichen: Die vielen Seiten sind die Eigentümer; Die eine Seite ist die Umkehrung. Sie haben keine Wahl (praktisch).
John
11
Nein, Hibernate hat das erfunden. Ich mag es nicht, da es einen Teil der Implementierung dem OO-Modell aussetzt. Ich würde eine @Parentoder eine @ChildAnmerkung anstelle von "XtoY" bevorzugen, um anzugeben, was die Verbindung bedeutet (anstatt wie sie implementiert wird )
Aaron Digulla
4
@AaronDigulla Jedes Mal, wenn ich eine OneToMany-Zuordnung durchgehen muss, bin ich gekommen, um diese Antwort zu lesen, wahrscheinlich die beste zu diesem Thema auf SO.
Eugene
7
Beeindruckend. Wenn nur die Dokumentation der ORM-Frameworks eine so gute Erklärung hätte - das würde das Schlucken erleichtern! Hervorragende Antwort!
NickJ
2
@klausch: Die Hibernate-Dokumentation ist verwirrend. Ignoriere es. Sehen Sie sich den Code, die SQL in der Datenbank und die Funktionsweise der Fremdschlüssel an. Wenn Sie möchten, können Sie ein Stück Weisheit mit nach Hause nehmen: Dokumentation ist eine Lüge. Benutze die Quelle, Luke.
Aaron Digulla
41

Unglaublicherweise hat in 3 Jahren niemand Ihre ausgezeichnete Frage mit Beispielen für beide Arten der Abbildung der Beziehung beantwortet.

Wie von anderen erwähnt, enthält die "Eigentümer" -Seite den Zeiger (Fremdschlüssel) in der Datenbank. Sie können jede Seite als Eigentümer festlegen. Wenn Sie jedoch die eine Seite als Eigentümer festlegen, ist die Beziehung nicht bidirektional (die inverse, auch als "viele" bezeichnete Seite hat keine Kenntnis von ihrem "Eigentümer"). Dies kann für die Einkapselung / lose Kopplung wünschenswert sein:

// "One" Customer owns the associated orders by storing them in a customer_orders join table
public class Customer {
    @OneToMany(cascade = CascadeType.ALL)
    private List<Order> orders;
}

// if the Customer owns the orders using the customer_orders table,
// Order has no knowledge of its Customer
public class Order {
    // @ManyToOne annotation has no "mappedBy" attribute to link bidirectionally
}

Die einzige bidirektionale Mapping-Lösung besteht darin, dass die Seite "Viele" ihren Zeiger auf die "Eins" besitzt und das Attribut "mappedBy" von @OneToMany verwendet. Ohne das Attribut "mappedBy" erwartet Hibernate eine doppelte Zuordnung (die Datenbank würde sowohl die Join-Spalte als auch die Join-Tabelle enthalten, was redundant ist (normalerweise unerwünscht)).

// "One" Customer as the inverse side of the relationship
public class Customer {
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
    private List<Order> orders;
}

// "many" orders each own their pointer to a Customer
public class Order {
    @ManyToOne
    private Customer customer;
}
Steve Jones
quelle
2
In Ihrem unidirektionalen Beispiel erwartet JPA, dass eine zusätzliche Tabelle customer_orders vorhanden ist. Mit JPA2 können Sie die Annotation @JoinColumn (die ich anscheinend häufig verwende) im Auftragsfeld des Kunden verwenden, um die Datenbank-Fremdschlüsselspalte in der Auftragstabelle anzugeben, die verwendet werden soll. Auf diese Weise haben Sie eine unidirektionale Beziehung in Java, während die Tabelle "Order" noch eine Fremdschlüsselspalte enthält. In der Objektwelt weiß die Bestellung nichts über den Kunden, während in der Datenbankwelt der Kunde nichts über die Bestellung weiß.
Henno Vermeulen
1
Um vollständig zu sein, können Sie den bidirektionalen Fall anzeigen, in dem der Kunde die Eigentümerseite der Beziehung ist.
HDave
35

Die Entität, die die Tabelle mit dem Fremdschlüssel in der Datenbank enthält, ist die besitzende Entität, und die andere Tabelle, auf die verwiesen wird, ist die inverse Entität.

Venu
quelle
30
noch einfacher: Besitzer ist der Tisch mit der FK Column
Jacktrades
2
Einfache und gute Erklärung. Jede Seite kann zum Eigentümer gemacht werden. Wenn wir mappedBy in Order.java im Feld Customer <mappedby aus Customer.java entfernen> verwenden, wird eine neue Tabelle wie Order_Customer erstellt, die zwei Spalten enthält. ORDER_ID und CUSTOMER_ID.
HakunaMatata
14

Einfache Regeln für bidirektionale Beziehungen:

1.Für viele bidirektionale Beziehungen von vielen zu eins ist die viele Seite immer die besitzende Seite der Beziehung. Beispiel: 1 Raum hat viele Personen (eine Person gehört nur einem Raum) -> Besitzerseite ist Person

2. Bei bidirektionalen Eins-zu-Eins-Beziehungen entspricht die besitzende Seite der Seite, die den entsprechenden Fremdschlüssel enthält.

3.Für viele zu viele bidirektionale Beziehungen kann jede Seite die besitzende Seite sein.

Hoffnung kann dir helfen.

Ken Block
quelle
Warum brauchen wir überhaupt einen Besitzer und eine Umkehrung? Wir haben bereits die sinnvollen Konzepte von einseitig und vielseitig und es spielt keine Rolle, wer in vielen zu vielen Situationen der Eigentümer ist. Was sind die Konsequenzen der Entscheidung? Es ist kaum zu glauben, dass jemand, der so linkssinnig ist wie ein Datenbankingenieur, beschlossen hat, diese redundanten Konzepte zu prägen.
Dan Cancro
3

Für zwei Entitätsklassen Kunde und Auftrag erstellt der Ruhezustand zwei Tabellen.

Mögliche Fälle:

  1. mappedBy wird in den Klassen Customer.java und Order.java then-> nicht verwendet

    Auf Kundenseite wird eine neue Tabelle [name = CUSTOMER_ORDER] erstellt, die die Zuordnung von CUSTOMER_ID und ORDER_ID beibehält. Dies sind Primärschlüssel von Kunden- und Auftragstabellen. Auf der Auftragsseite ist eine zusätzliche Spalte erforderlich, um die entsprechende Customer_ID-Datensatzzuordnung zu speichern.

  2. mappedBy wird in Customer.java verwendet [Wie in der Problembeschreibung angegeben]. Jetzt wird keine zusätzliche Tabelle [CUSTOMER_ORDER] erstellt. Nur eine Spalte in der Auftragstabelle

  3. mappedby wird in Order.java verwendet. Jetzt wird eine zusätzliche Tabelle im Ruhezustand erstellt. [name = CUSTOMER_ORDER] Die Order Table enthält keine zusätzliche Spalte [Customer_ID] für die Zuordnung.

Jede Seite kann zum Eigentümer der Beziehung gemacht werden. Aber es ist besser, xxxToOne Seite zu wählen.

Codierungseffekt -> Nur die Besitzerseite der Entität kann den Beziehungsstatus ändern. Im folgenden Beispiel ist die BoyFriend-Klasse Eigentümer der Beziehung. Selbst wenn Freundin sich trennen will, kann sie nicht.

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "BoyFriend21")
public class BoyFriend21 {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Boy_ID")
    @SequenceGenerator(name = "Boy_ID", sequenceName = "Boy_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
    private Integer id;

    @Column(name = "BOY_NAME")
    private String name;

    @OneToOne(cascade = { CascadeType.ALL })
    private GirlFriend21 girlFriend;

    public BoyFriend21(String name) {
        this.name = name;
    }

    public BoyFriend21() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BoyFriend21(String name, GirlFriend21 girlFriend) {
        this.name = name;
        this.girlFriend = girlFriend;
    }

    public GirlFriend21 getGirlFriend() {
        return girlFriend;
    }

    public void setGirlFriend(GirlFriend21 girlFriend) {
        this.girlFriend = girlFriend;
    }
}

import org.hibernate.annotations.*;
import javax.persistence.*;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.List;

@Entity 
@Table(name = "GirlFriend21")
public class GirlFriend21 {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Girl_ID")
    @SequenceGenerator(name = "Girl_ID", sequenceName = "Girl_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
    private Integer id;

    @Column(name = "GIRL_NAME")
    private String name;

    @OneToOne(cascade = {CascadeType.ALL},mappedBy = "girlFriend")
    private BoyFriend21 boyFriends = new BoyFriend21();

    public GirlFriend21() {
    }

    public GirlFriend21(String name) {
        this.name = name;
    }


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public GirlFriend21(String name, BoyFriend21 boyFriends) {
        this.name = name;
        this.boyFriends = boyFriends;
    }

    public BoyFriend21 getBoyFriends() {
        return boyFriends;
    }

    public void setBoyFriends(BoyFriend21 boyFriends) {
        this.boyFriends = boyFriends;
    }
}


import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.Arrays;

public class Main578_DS {

    public static void main(String[] args) {
        final Configuration configuration = new Configuration();
         try {
             configuration.configure("hibernate.cfg.xml");
         } catch (HibernateException e) {
             throw new RuntimeException(e);
         }
        final SessionFactory sessionFactory = configuration.buildSessionFactory();
        final Session session = sessionFactory.openSession();
        session.beginTransaction();

        final BoyFriend21 clinton = new BoyFriend21("Bill Clinton");
        final GirlFriend21 monica = new GirlFriend21("monica lewinsky");

        clinton.setGirlFriend(monica);
        session.save(clinton);

        session.getTransaction().commit();
        session.close();
    }
}

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.List;

public class Main578_Modify {

    public static void main(String[] args) {
        final Configuration configuration = new Configuration();
        try {
            configuration.configure("hibernate.cfg.xml");
        } catch (HibernateException e) {
            throw new RuntimeException(e);
        }
        final SessionFactory sessionFactory = configuration.buildSessionFactory();
        final Session session1 = sessionFactory.openSession();
        session1.beginTransaction();

        GirlFriend21 monica = (GirlFriend21)session1.load(GirlFriend21.class,10);  // Monica lewinsky record has id  10.
        BoyFriend21 boyfriend = monica.getBoyFriends();
        System.out.println(boyfriend.getName()); // It will print  Clinton Name
        monica.setBoyFriends(null); // It will not impact relationship

        session1.getTransaction().commit();
        session1.close();

        final Session session2 = sessionFactory.openSession();
        session2.beginTransaction();

        BoyFriend21 clinton = (BoyFriend21)session2.load(BoyFriend21.class,10);  // Bill clinton record

        GirlFriend21 girlfriend = clinton.getGirlFriend();
        System.out.println(girlfriend.getName()); // It will print Monica name.
        //But if Clinton[Who owns the relationship as per "mappedby" rule can break this]
        clinton.setGirlFriend(null);
        // Now if Monica tries to check BoyFriend Details, she will find Clinton is no more her boyFriend
        session2.getTransaction().commit();
        session2.close();

        final Session session3 = sessionFactory.openSession();
        session1.beginTransaction();

        monica = (GirlFriend21)session3.load(GirlFriend21.class,10);  // Monica lewinsky record has id  10.
        boyfriend = monica.getBoyFriends();

        System.out.println(boyfriend.getName()); // Does not print Clinton Name

        session3.getTransaction().commit();
        session3.close();
    }
}
HakunaMatata
quelle
1

Tabellenbeziehungen vs. Entitätsbeziehungen

In einem relationalen Datenbanksystem kann es nur drei Arten von Tabellenbeziehungen geben:

  • Eins-zu-Viele (über eine Fremdschlüsselspalte)
  • eins zu eins (über einen gemeinsamen Primärschlüssel)
  • Viele-zu-Viele (über eine Verknüpfungstabelle mit zwei Fremdschlüsseln, die auf zwei separate übergeordnete Tabellen verweisen)

Eine one-to-manyTabellenbeziehung sieht also wie folgt aus:

<code> Eins-zu-Viele </ code> -Tabellenbeziehung

Beachten Sie, dass die Beziehung auf der Spalte Fremdschlüssel (z. B. post_id) in der untergeordneten Tabelle basiert .

Es gibt also eine einzige Quelle der Wahrheit, wenn es darum geht, eine one-to-manyTabellenbeziehung zu verwalten.

Wenn Sie nun eine bidirektionale Entitätsbeziehung verwenden, die der one-to-manyzuvor gesehenen Tabellenbeziehung zugeordnet ist:

Bidirektionale <code> Eins-zu-Viele </ code> -Entitätszuordnung

Wenn Sie sich das obige Diagramm ansehen, sehen Sie, dass es zwei Möglichkeiten gibt, diese Beziehung zu verwalten.

In der PostEntität haben Sie die commentsSammlung:

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

In der PostCommentwird die postZuordnung wie folgt zugeordnet:

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

Sie haben also zwei Seiten, die die Entitätszuordnung ändern können:

  • Durch Hinzufügen eines Eintrags in der commentsuntergeordneten Auflistung post_commentsollte der übergeordneten postEntität über ihre eine neue Zeile zugeordnet werdenpost_id Spalte .
  • Durch Festlegen der postEigenschaft der PostCommentEntität sollte auch die post_idSpalte aktualisiert werden.

Da es zwei Möglichkeiten gibt, die Fremdschlüsselspalte darzustellen, müssen Sie definieren, welche Quelle die Wahrheit ist, wenn es darum geht, die Zuordnungsstatusänderung in die entsprechende Änderung des Fremdschlüsselspaltenwerts zu übersetzen.

MappedBy (auch bekannt als die inverse Seite)

Das mappedByAttribut gibt an, dass die @ManyToOneSeite für die Verwaltung der Fremdschlüsselspalte verantwortlich ist. Die Auflistung wird nur zum Abrufen der untergeordneten Entitäten und zum Kaskadieren von Statusänderungen der übergeordneten Entität an untergeordnete Entitäten verwendet (z. B. sollte durch Entfernen der übergeordneten Entitäten auch die untergeordneten Entitäten entfernt werden).

Es wird als inverse Seite bezeichnet, da es auf die untergeordnete Entitätseigenschaft verweist, die diese Tabellenbeziehung verwaltet.

Synchronisieren Sie beide Seiten einer bidirektionalen Zuordnung

Selbst wenn Sie das mappedByAttribut definiert haben und die untergeordnete @ManyToOneZuordnung die Spalte Fremdschlüssel verwaltet, müssen Sie jetzt beide Seiten der bidirektionalen Zuordnung synchronisieren.

Der beste Weg, dies zu tun, besteht darin, diese beiden Dienstprogrammmethoden hinzuzufügen:

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

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

Die Methoden addCommentund stellen removeCommentsicher, dass beide Seiten synchronisiert sind. Wenn wir also eine untergeordnete Entität hinzufügen, muss die untergeordnete Entität auf die übergeordnete Entität verweisen, und die übergeordnete Entität sollte das untergeordnete Element in der untergeordneten Sammlung enthalten.

Weitere Informationen zum besten Synchronisieren aller bidirektionalen Entitätszuordnungstypen finden Sie in diesem Artikel .

Vlad Mihalcea
quelle