JPA @OneToManyアソシエーションを使用する場合の@JoinColumnとmappedByの違いは何ですか


516

違いは何ですか:

@Entity
public class Company {

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

そして

@Entity
public class Company {

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

1
また、関連する問題の非常に良い説明については、ORMマッピングの質問の所有側とは何かを参照してください。
dirkt 2018年

回答:


545

注釈@JoinColumnは、このエンティティが関係の所有者であることを示します(つまり、対応するテーブルには、参照されたテーブルへの外部キーを持つ列があります)。mappedByは、この側のエンティティが関係の逆であることを示します。所有者は「その他」のエンティティに存在します。これは、 "mappedBy"(完全な双方向の関係)で注釈を付けたクラスから他のテーブルにアクセスできることも意味します。

特に、問題のコードの場合、正しい注釈は次のようになります。

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

3
どちらの場合も、支店には会社IDのフィールドがあります。
Mykhaylo Adamovych 2012

3
Companyテーブルには、参照されたテーブルへの外部キーを持つ列がありません-ブランチはCompanyを参照しています。なぜ「対応するテーブルには、参照されたテーブルへの外部キーを持つ列がある」と言っているのですか?もう少し説明してください。
Mykhaylo Adamovych 2012

13
@MykhayloAdamovychサンプルコードで回答を更新しました。それは使用に間違いだということに注意してください@JoinColumnCompany
オスカル・ロペス

10
@MykhayloAdamovych:いいえ、実際はそうではありません。場合Branchプロパティの参照を持っていないCompanyが、基礎となる表がない列を持っている、あなたは使用することができ@JoinTable、それをマップします。通常、テーブルに対応するオブジェクトの列をマップするため、これは異常な状況ですが、これは発生する可能性があり、完全に正当です。
トムアンダーソン

4
これは、ORMを嫌う別の理由です。ドキュメンテーションは、あまりに危険な場合が多く、私の本では、これはあまりにも多くの魔法の領域で蛇行しています。私はこの問題に苦労しており、の単語ごとに続く@OneToOneと、子行nullは、親を参照するFKey 列ので更新されます。
Ashesh

225

@JoinColumn関係の両側で使用できます。問題は@JoinColumn@OneToManyサイドでの使用(まれなケース)に関するものでした。そして、ここでのポイントは、いくつかの追加のステートメントを生成する最適化されいないSQLクエリと共に、物理情報の複製(列名)にありますUPDATE

ドキュメントによると

ので、いずれかの多くはされ(ほとんど)常に所有者側 JPA仕様で双方向の関係は、多くの関連するものがによって注釈されています@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() {
    ...
} 

TroopSoldier部隊プロパティを通じて双方向の1対多の関係があります。で物理的なマッピングを定義する必要はありません(必須ではありません)。mappedByサイドで。

1 対多の側を所有側として双方向の1を多にマッピングするには、mappedBy要素を削除し、多を1に@JoinColumninsertableまた多updatableにfalseに設定する必要があります。このソリューションは最適化されておらず、いくつかの追加UPDATEステートメントが生成されます。

@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() {
    ...
}

1
Troopを参照する外部キーが含まれているため、2番目のスニペットでTroopがどのように所有者になることができるかを理解できません。ソルジャーが引き続き所有者です。(私はmysqlを使用していますが、あなたのアプローチで確認しました)。
Akhilesh 2013年

10
あなたの例では、注釈mappedBy="troop"はどのフィールドを参照していますか?
Fractaliste 2014年

5
@FractalisteアノテーションmappedBy="troop"はクラスSoldierのプロパティ部隊を参照します。上記のコードでは、Mykhayloが省略しているため、プロパティは表示されませんが、ゲッターgetTroop()によってその存在を推測できます。オスカー・ロペスの答えをチェックしてください、それは非常に明確であり、あなたはポイントを得るでしょう。
nicolimo86

1
この例は、JPA 2仕様の乱用です。著者の目的が双方向の関係を作成することである場合、親側でmappedByを使用し、子側でJoinColumn(必要な場合)を使用する必要があります。ここで紹介するアプローチでは、2つの単方向の関係が得られます。独立していますが、運がよければ(誤用により)、これら2つの関係は同じ外部キーを使用して定義されます
aurelije

1
JPA 2.xを使用している場合、以下の私の答えは少しわかりやすくなっています。両方のルートを試し、テーブルを生成するときにHibernateが何をするかを確認することをお勧めします。新しいプロジェクトに参加している場合は、ニーズに合うと思う世代を選択してください。レガシーデータベースを使用していて、構造を変更したくない場合は、スキーマに一致する方を選択してください。
Snekse 2017年

65

これは非常に一般的な質問であるため、 この回答に基づいてこの記事を作成しました。

単方向の1対多の関連付け

この記事で説明したように、で@OneToManyアノテーションを使用すると@JoinColumn、次の図の親Postエンティティと子の間のような単方向の関連付けができますPostComment

単方向の1対多の関連付け

単方向の1対多の関連付けを使用する場合、親側のみが関連付けをマップします。

この例では、Postエンティティのみが子エンティティ@OneToManyへの関連付けを定義しますPostComment

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

双方向の1対多の関連付け

属性セットでを使用する@OneToManyと、mappedBy双方向の関連付けができます。この例では、次の図に示すように、両方のPostエンティティにPostComment子エンティティのコレクションがあり、子PostCommentエンティティには親Postエンティティへの参照があります。

双方向の1対多の関連付け

PostCommentエンティティ、post次のようにエンティティのプロパティがマッピングされています。

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

我々が明示的に設定された理由fetchに属性はFetchType.LAZY、デフォルトでは、すべてのためである@ManyToOne@OneToOne組合は、N + 1つのクエリ問題を引き起こす可能性が、熱心にフェッチされます。このトピックの詳細については、こちらの記事をご覧ください。

Postエンティティ、comments次のように関連付けがマップされています。

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

アノテーションのmappedBy属性は子エンティティのプロパティを@OneToMany参照しpostますPostComment。これにより、Hibernateは双方向の関連付けが@ManyToOneサイドサイドの関係がこのテーブルの関係のベースとなる外部キー列の値の管理を担当します。

双方向関連について、あなたはまた、2つのユーティリティメソッドを持っている必要がありますようにaddChildremoveChild

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

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

これらの2つの方法により、双方向の関連付けの両側が同期されなくなります。両端を同期しないと、Hibernateはアソシエーション状態の変更がデータベースに伝播することを保証しません。

双方向の関連付けをJPAおよびHibernateと同期させるのに最適なワットの詳細については、こちらの記事をご覧ください。

どれを選ぶ?

単方向@OneToMany関連は非常によく実行されません。あなたはそれを避ける必要がありますので、。

より効率的な双方向@OneToMany使用する方がよいでしょう。


32

アノテーションmappedByは、理想的には常に双方向の関係の親側(会社クラス)で使用する必要があります。この場合は、子クラス(ブランチクラス)のメンバー変数 'c​​ompany'を指す会社クラスにある必要があります。

アノテーション@JoinColumnは、エンティティの関連付けを結合するためのマップされた列を指定するために使用されます。このアノテーションは、任意のクラス(親または子)で使用できますが、片側(親クラスまたは子クラスではなく)でのみ使用するのが理想的です両方)ここでは、この場合、Branchクラスの外部キーを示す双方向関係の子側(Branchクラス)で使用しました。

以下は実際の例です:

親クラス、会社

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


}

子クラス、ブランチ

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


}

20

私は追加するにはちょうどたい@JoinColumnいつもに関係する必要はありません物理的な情報の場所として、この答えは示唆しています。親テーブルに子テーブルを指すテーブルデータがない場合でも、@JoinColumnと組み合わせることができ@OneToManyます。

JPAで単方向のOneToMany関係を定義する方法

単方向のOneToMany、逆のManyToOne、結合テーブルなし

それだけで利用できるようJPA 2.x+です。これは、完全な参照ではなく、子クラスに親のIDのみを含めたい場合に役立ちます。


そうです、結合テーブルなしの単方向OneToManyのサポートがJPA2で導入されました
aurelije

17

私はオスカー・ロペスのここで受け入れられた答えに同意しません。その答えは不正確です!

@JoinColumnこのエンティティが関係の所有者であることを示すものではありません。代わりに、これを行うのは@ManyToOne注釈です(彼の例では)。

以下のような関係の注釈@ManyToOne@OneToManyおよび@ManyToManyするJPA / Hibernateのを教えてマッピングを作成します。 デフォルトでは、これは別の結合テーブルを介して行われます。


@JoinColumn

の目的は@JoinColumn結合列がまだ存在しない場合に作成することです。存在する場合、この注釈を使用して結合列に名前を付けることができ ます。


MappedBy

このMappedByパラメーターの目的は、JPAに指示することです。関係はこの関係の反対側のエンティティーによってすでにマップされているため、別の結合テーブルを作成しないでください。



覚えておいてください: MappedByは、2つのエンティティを関連付けるメカニズムを生成することを目的とするリレーションシップアノテーションのプロパティであり、デフォルトでは結合テーブルを作成することで関連付けます。 MappedBy一方向の処理を停止します。

マッピングのメカニズムは外部キーフィールドに対する3つのマッピングアノテーションの1つを使用してクラス内で指示されるため、使用していないエンティティは関係MappedBy所有者と呼ばれます。これは、マッピングの性質を指定するだけでなく、結合テーブルの作成も指示します。さらに、代わりに所有者エンティティのテーブル内に保持する外部キーに@JoinColumnアノテーションを適用することで、結合テーブルを抑制するオプションも存在します。

つまり、要約@JoinColumnすると、新しい結合列を作成するか、既存の列の名前を変更します。ながら、MappedByパラメータが他の(子)クラスのリレーションシップアノテーションと連携して機能する結合テーブルを介して、または所有者エンティティの関連テーブルに外部キー列を作成することによって、マッピングを作成します。

仕組みを説明するためにMapppedBy、以下のコードを検討してください。場合はMappedBy、パラメータが削除された場合、休止状態は、実際には2つのテーブルを結合作成します!どうして?多対多の関係には対称性があり、Hibernateは一方の方向をもう一方の方向に選択する根拠がないためです。

したがってMappedBy、私たちはHibernateに伝えるために使用し、2つのエンティティ間の関係のマッピングを指示するために他のエンティティを選択しまし

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

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

@JoinColumn(name = "driverID")を所有者クラス(以下を参照)に追加すると、結合テーブルが作成されなくなり、代わりにCarsテーブルにdriverID外部キー列を作成してマッピングを構築します。

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

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

1

JPAは階層化されたAPIであり、さまざまなレベルに独自の注釈があります。最高レベルは、(1)永続クラスを記述するエンティティレベルであり、(2)エンティティがリレーショナルデータベースにマップされていることを前提とするリレーショナルデータベースレベルと、(3)Javaモデルがあります。

レベル1注釈:@Entity@Id@OneToOne@OneToMany@ManyToOne@ManyToMany。これらの高レベルのアノテーションのみを使用して、アプリケーションに永続性を導入できます。ただし、JPAの想定に従ってデータベースを作成する必要があります。これらの注釈は、エンティティ/関係モデルを指定します。

レベル2の注釈:@Table@Column@JoinColumnあなたはJPAのデフォルトまたは既存のデータベースにマッピングする必要がある場合は満足していない場合、...リレーショナルデータベースの表/列にエンティティ/プロパティからのマッピングに影響を与えます。これらの注釈は実装注釈と見なすことができ、マッピングの方法を指定します。

私の意見では、高レベルの注釈にできるだけ固執し、必要に応じて低レベルの注釈を導入するのが最善です。

質問に答えるには、@OneToMany/ mappedByはエンティティドメインからの注釈のみを使用するため、最も優れています。@oneToMany/ @JoinColumnも結構ですが、それは、これは厳密には必要ではない実装アノテーションを使用しています。


1

簡単にしましょう。マッピングに関係なく、@ JoinColumnをどちらの側でも
使用できます。

これを3つのケースに分けましょう。
1)支店から会社への単方向マッピング。
2)会社から支店への双方向マッピング。
3)会社から支店への単一方向マッピングのみ。

したがって、どのユースケースもこの3つのカテゴリに分類されます。@JoinColumnmappedByの使い方を説明しましょう。
1)支店から会社への単方向マッピング。ブランチテーブルでJoinColumn
使用します。 2)会社から支店への双方向マッピング。@Mykhaylo Adamovychの回答で説明されているように、CompanyテーブルでmappedByを 使用します。 3)会社から支店への一方向のマッピング。Companyテーブルで@JoinColumnを 使用するだけです。



@Entity
public class Company {

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

これは、branchsテーブルの外部キー「courseId」マッピングに基づいて、すべてのブランチのリストを取得することを示しています。注:この場合、ブランチから会社をフェッチすることはできません。会社からブランチへの単一方向マッピングのみが存在します。

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