双方向のJPA OneToMany / ManyToOne関連付けにおける「関連付けの逆側」とは何ですか?


167

例セクションの@OneToManyJPA注釈参照

例1-59 @OneToMany-ジェネリックスを持つCustomerクラス

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

例1-60 @ManyToOne-ジェネリックスを持つ注文クラス

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

Customerエンティティは協会の所有者であるように私には思えます。ただし、mappedBy同じドキュメントの属性の説明では、次のように書かれています。

関係が双方向の場合、例1-60に示すように、関連付けの逆(非所有)側のmappedBy要素を、関係を所有するフィールドまたはプロパティの名前に設定します。

しかし、私が間違っていなければ、例のように見えます、 mappedByます。実際には、非所有側ではなく、関連付けの所有側で指定されています。

だから私の質問は基本的に:

  1. 双方向(1対多/多対1)の関連付けでは、どのエンティティが所有者ですか?片側を所有者として指定するにはどうすればよいですか?多くの側を所有者として指定するにはどうすればよいですか?

  2. 「関連の逆側」とはどういう意味ですか?片側を反転として指定するにはどうすればよいですか?多面を逆に指定するにはどうすればよいですか?


1
指定したリンクは古くなっています。更新してください。
MartinL 2012

回答:


306

これを理解するには、少し前に戻る必要があります。OOでは、顧客が注文を所有します(注文は顧客オブジェクトのリストです)。顧客なしで注文することはできません。したがって、顧客は注文の所有者のようです。

しかし、SQLの世界では、実際には1つのアイテムに他へのポインタが含まれます。N個の注文に対して1人の顧客がいるため、各注文には、それが属する顧客への外部キーが含まれています。これは「接続」であり、これは接続(情報)を「所有」する(または文字どおりに含む)順序を意味します。これは、オブジェクト指向/モデルの世界とは正反対です。

これは理解に役立つかもしれません:

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

反対側はオブジェクトのOO「所有者」であり、この場合は顧客です。顧客には、注文を保存するための列がテーブルにないため、注文テーブルのどこにこのデータを保存できるかを通知する必要があります(これはを介して行われますmappedBy)。

もう1つの一般的な例は、親と子の両方になるノードを持つツリーです。この場合、2つのフィールドが1つのクラスで使用されます。

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

これは、「外部キー」の多対1の設計作業を説明します。別のテーブルを使用して関係を維持する2番目のアプローチがあります。つまり、最初の例では、3つのテーブルがあります。1つは顧客、もう1つは注文、2列のテーブルは主キーのペア(customerPK、orderPK)です。

このアプローチは、上記のアプローチよりも柔軟性があります(1対1、多対1、1対多、さらには多対多でも簡単に処理できます)。価格は

  • 少し遅い(別のテーブルを維持して結合するために、2つではなく3つのテーブルを使用する)、
  • 結合構文はより複雑です(たとえば、何かをデバッグしようとするときなど、多くのクエリを手動で作成する必要がある場合は、面倒な場合があります)。
  • 接続テーブルを管理するコードで問題が発生すると、突然結果が多すぎたり少なすぎたりする可能性があるため、エラーが発生しやすくなります。

だから私はこのアプローチをめったに勧めません。


36
明確にするために:多くの側が所有者です。一方は逆です。選択肢はありません(実際には)。
ジョン

11
いいえ、Hibernateがこれを発明しました。実装の一部がOOモデルに公開されるため、私はそれが好きではありません。「XtoY」ではなく、@Parentまたは@Childアノテーションを使用して、接続の意味を実装方法ではなく)示します
Aaron Digulla

4
@AaronDigulla私がOneToManyマッピングを実行する必要があるたびに、私はこの回答を読むようになりました。おそらく、SOのテーマで最高です。
Eugene

7
ワオ。ORMフレームワークのドキュメンテーションだけがそのような良い説明を持っていたとしたら、全体が飲み込みやすくなります!正解です。
NickJ 2013

2
@klausch:Hibernateのドキュメントは混乱しています。それを無視します。コード、データベース内のSQL、および外部キーのしくみを見てください。必要に応じて、知識の一部を家に持ち帰ることができます。ドキュメンテーションは嘘です。ソース、ルークを使用してください。
アーロンディグラ

41

信じられないことに、3年間で、関係をマッピングする両方の方法の例を挙げて、誰もあなたのすばらしい質問に答えていません。

他の人が述べたように、「所有者」側には、データベース内のポインター(外部キー)が含まれています。どちら側を所有者として指定することもできますが、一方を所有者として指定すると、関係は双方向になりません(逆に、「多くの」側はその「所有者」を認識しません)。これは、カプセル化/疎結合に望ましい場合があります。

// "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
}

唯一の双方向マッピングソリューションは、「多」側に「1」へのポインタを所有させ、@ OneToMany「mappedBy」属性を使用することです。「mappedBy」属性がない場合、Hibernateは二重マッピングを期待します(データベースには結合列と結合テーブルの両方があり、冗長です(通常は望ましくありません))。

// "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;
}

2
単方向の例では、JPAは余分なcustomer_ordersテーブルが存在することを期待しています。JPA2では、Customerの注文フィールドで@JoinColumnアノテーション(よく使用するようです)を使用して、使用する必要があるOrderテーブルのデータベース外部キー列を示すことができます。このようにして、Javaで単一方向の関係を維持しながら、Orderテーブルに外部キー列を保持します。したがって、オブジェクトの世界では、注文は顧客について知りませんが、データベースの世界では、顧客は注文について知りません。
Henno Vermeulen 2013

1
完全なものにするために、顧客が関係の所有者である双方向のケースを示すことができます。
HDave 14年

35

データベース内に外部キーを持つテーブルを持つエンティティは所有エンティティであり、ポイントされている他のテーブルは逆エンティティです。


30
さらにシンプル:所有者はFK列を持つテーブルです
jacktrades

2
シンプルで良い説明。どちらの側もオーナーにすることができます。Order.javaでmappedByを使用すると、Customerフィールド<Customer.javaからmappedbyを削除>で、2つの列を持つOrder_Customerのような新しいテーブルが作成されます。ORDER_IDおよびCUSTOMER_ID。
HakunaMatata 2013

14

双方向関係の単純なルール:

1.多対1の双方向の関係の場合、多側は常に関係の所有側です。例:1つの部屋に多数の人物がいる(1人の人物が1つの部屋にのみ属している)->所有する側が人物である

2. 1対1の双方向関係の場合、所有側は、対応する外部キーを含む側に対応します。

3.多対多の双方向関係の場合、どちらの側も所有側になることができます。

希望があなたを助けることができます。


なぜオーナーとインバースが必要なのですか?私たちはすでに片側と多面の意味のある概念を持っています、そして多対多の状況で誰が所有者であるかは重要ではありません。決定の結果は何ですか?データベースエンジニアのような左脳の人がこれらの冗長な概念を作り出すことを決定したとは信じがたいです。
Dan Cancro 2016年

3

2つのエンティティークラスCustomerおよびOrde​​rの場合、hibernateは2つのテーブルを作成します。

考えられるケース:

  1. mappedByはCustomer.javaおよびOrde​​r.javaクラスでは使用されません->

    顧客側で、CUSTOMER_IDとORDER_IDのマッピングを維持する新しいテーブルが作成されます[name = CUSTOMER_ORDER]。これらは、顧客テーブルと注文テーブルの主キーです。注文側では、対応するCustomer_IDレコードマッピングを保存するために追加の列が必要です。

  2. mappedByはCustomer.javaで使用されます[問題のステートメントで指定されているとおり]追加のテーブル[CUSTOMER_ORDER]は作成されません。注文テーブルの1列のみ

  3. mappedbyはOrder.javaで使用されます。追加のテーブルがhibernateによって作成されます。

どのサイドも関係の所有者にすることができます。ただし、xxxToOne側を選択することをお勧めします。

コーディング効果->エンティティの所有側のみが関係ステータスを変更できます。以下の例では、BoyFriendクラスが関係の所有者です。ガールフレンドが別れたいと思っても、彼女はできません。

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();
    }
}

1

テーブルの関係とエンティティの関係

リレーショナルデータベースシステムでは、3種類のテーブルリレーションシップのみが存在できます。

  • 1対多(外部キー列を使用)
  • 1対1(共有主キーを介して)
  • 多対多(2つの別個の親テーブルを参照する2つの外部キーを持つリンクテーブルを介して)

したがって、one-to-manyテーブルの関係は次のようになります。

<code> 1対多</ code>テーブルの関係

関係はpost_id、子テーブルの外部キー列(たとえば、)に基づいていることに注意してください。

したがって、one-to-manyテーブルのリレーションシップを管理することに関しては、単一の信頼できる情報源があります。

ここで、one-to-many前に見たテーブルリレーションシップにマップする双方向エンティティリレーションシップを取得すると、次のようになります。

双方向の<code> One-To-Many </ code>エンティティの関連付け

上の図を見ると、この関係を管理する方法が2つあることがわかります。

ではPostエンティティ、あなたが持っているcommentsコレクションを:

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

また、PostCommentでは、post関連付けは次のようにマッピングされます。

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

したがって、エンティティの関連付けを変更できる2つの側面があります。

  • comments子コレクションにエントリを追加することにより、新しいpost_commentpostをその親エンティティに関連付ける必要がありますpost_id列をます。
  • エンティティのpostプロパティを設定するPostCommentと、post_id列も更新されます。

外部キー列を表す方法は2つあるため、関連付けの状態の変化を対応する外部キー列の値の変更に変換する場合、どちらが真のソースであるかを定義する必要があります。

MappedBy(別名逆側)

このmappedBy属性は、@ManyToOne側面が外部キー列の管理を担当することを示し、コレクションは子エンティティをフェッチし、親エンティティの状態変更を子にカスケードするためにのみ使用されます(たとえば、親を削除すると子エンティティも削除されます)。

これは、このテーブルの関係を管理する子エンティティプロパティを参照するため、逆側と呼ばれます。

双方向の関連付けの両側を同期する

これで、mappedBy属性を定義し、子側の@ManyToOne関連付けが外部キー列を管理する場合でも、双方向の関連付けの両側を同期する必要があります。

これを行う最良の方法は、次の2つのユーティリティメソッドを追加することです。

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

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

addComment及びremoveComment方法は、両側が同期されていることを確認します。したがって、子エンティティを追加する場合、子エンティティは親を指す必要があり、親エンティティは子を子コレクションに含める必要があります。

すべての双方向エンティティ関連タイプを同期する最良の方法の詳細については、こちらの記事をご覧ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.