Most efficient way to map @OneToMany relationship with JPA and Hibernate

I chose to open with this quote because I’m a fan of Linus Torvalds.😉

This is my first ever article. この記事では、One-to-Many/Many-to-Oneエンティティの関連付けで考えられるすべてのケースをカバーする予定です。 残りのMany-to-ManyOne-to-Oneは次の記事でカバーします。

I hope this definitely help every newbies who want to learn jpa/hibernate, please read the whole piece 😛

NOTE:

Here I have covered all possible cases of One-to-Many/Many-to-One mapping.See I’ve got a lot to the case. 中でも、双方向の `@OneToMany` 関連は、1 対多のデータベース関係をマッピングする最良の方法です。

Hibernate 関連は One-to-OneOne-to-Many/Many-to-One および Many-to-Many に分類されます。

  • 関係の方向は、双方向または単方向のいずれかです。
  • 双方向の関係には、所有側と逆方向の側の両方があります。 リレーションシップの所有側は、Persistenceランタイムがデータベース内のリレーションシップに対してどのように更新を行うかを決定します。
  • 単方向リレーションは、一方の側がそのリレーションについて知らないリレーションです。 たとえば、Line ItemはProductを特定する関係フィールドを持ちますが、ProductはLine Itemの関係フィールドやプロパティを持ちません。 言い換えると、Line Item は Product について知っていますが、Product はどの Line Item インスタンスがそれを参照しているかを知りません。

双方向リレーションシップ:

  • 双方向リレーションシップは、双方向でナビゲート アクセスを提供するので、明示的に問い合わせをしなくても相手側にアクセスすることが可能です。 関係フィールドまたはプロパティを通じて、エンティティ クラスのコードはその関連オブジェクトにアクセスできます。 エンティティに関連フィールドがある場合、エンティティはその関連オブジェクトについて「知っている」と言われます。
    • 双方向関係の逆側は、@OneToOne@OneToMany、または@ManyToManyアノテーションのmappedBy要素を使用して、その所有側(外部キーを含むエンティティ)を参照する必要があります。 mappedBy要素には、リレーションの所有者であるエンティティのプロパティまたはフィールドを指定します。
    • @ManyToOne双方向関係のMany sideはmappedBy要素を定義してはいけません。 多側は常に関係の所有者側です。
    • @OneToOne 双方向リレーションシップの場合、所有側は@JoinColumnを含む側、つまり対応する外部キーに対応します。
    • @ManyToMany 双方向リレーションシップでは、どちらかの側がowning sideになる可能性があります。

    @OneToMany relationship with JPA and Hibernate

    簡単に言うと、1対多のマッピングとは表の中の1行を別の表の中の複数の行にマップすることを意味します。

    When to use one to many mapping

    Use one to mapping to create 1…N relationship between entities or objects.

    We have to write two entities i.e., but we have to write 1. CompanyBranchのように、複数の支店を1つの会社に関連付けることはできますが、1つの支店を2つ以上の会社の間で共有することはできないようにします。

    Hibernate の 1 対多のマッピングソリューション:

    1. 外部キー関連による 1 対多のマッピング
    2. 結合テーブルによる 1 対多数のマッピング

    この問題は 2 つの異なる方法で解決することができます。 このカラムは、会社テーブルの主キーを参照します。 この方法では、2つの支店が複数の会社に関連付けられることはありません。

    2番目の方法は、共通の結合テーブルを持つことです。例えば、Company_Branch, このテーブルには2つの列、すなわち、会社テーブルの主キーを参照する外部キーとなる company_id、同様に、支店テーブルの主キーを参照する外部キーとなる branch_id があります。

    @OneToMany/@ManyToOneが子側にそれぞれ@ManyToOne/@OneToMany関連をミラーリングしていない場合、@OneToMany/@ManyToOne関連は単方向性である。 会社が一対多の関係を宣言するか、支店が多対一の関係を宣言するか、どちらかです。 (外部キーによる関連付け)

    @OneToManyだけを使用する場合、3つのテーブルが存在することになります。 例えば、company, branch, company_branch.

    NOTE:
    上の例では、cascade、orphanRemoval、fetch および targetEntity という用語を使用しましたが、これは次の投稿で説明する予定です。

    テーブル company_branch は、2つの外部キー company_idbranch_id を持ちます。

    ここで、1 つの Company と 2 つの Branch(s) を持続させると、

    Hibernate は次の SQL 文を実行します:

    • @OneToMany 関連は定義により親(非所有の)関連ですが、それが単方向または双方向であっても、です。
    • Company エンティティを永続化するとき、カスケードは永続化操作を基にした Branch の子にも伝搬します。 branchs コレクションから Branch を削除すると、関連行はリンク テーブルから削除され、orphanRemoval 属性は同様に Branch 削除をトリガーします。

    子エンティティを削除する場合、単方向関連付けはあまり効率的ではありません。

    一方、双方向の @OneToMany 関連付けは、子エンティティが関連付けを制御するため、より効率的である。 5431>

    このコードでは、Company(company_id,name)Branch(branch_id,name,company_company_id)の2つのテーブルが生成されます。

    CASE 3: (外部キー関連付けのあるマッピング)

    @ManyToOne@OneToManyの両方を使用すると、3つのテーブル Company(id,name), Branch(id,name,company_id), Company_Branch(company_id,branch_id)

    この下のマッピングは双方向のように見えるでしょうがそうではありません。

    CASE 4.両方向関係ではなく、単方向の関係2つが定義されています。 (一方向@OneToMany with @JoinColumn)(外部キー関連付けによるマッピング)

    @OneToMany@JoinColumnを使う場合、2つのテーブルが存在することになります。 例えば、会社、支店

    上記のエンティティで@JoinColumn内のnameは外部キー列名companyIdすなわちcompanyを指します。

    CASE 5: (外部キーとの関連付け)

    @ManyToOne@JoinColumnを使用する場合、2つのテーブルが存在することになります。

    @JoinColumn注釈は、Hibernateがこの関連を定義するcompany_id外部キー列がbranchテーブル内にあることを理解するのに役立つ。

    Branch は外部キー company_id を持つことになるので、オーナー側となります。

    ここで、1つのCompanyと2つのBranchを保持する場合:

    子コレクションから最初のエントリを削除する場合:

    company.getBranches().remove(0);

    Hibernateは1つではなく2つのステートメントを実行します:

    • 最初に外部キー項目をヌルにし(親との連携を断つ)、レコードを削除します
    Update branch set branch_id = null where where id = 1 delete from branch where id = 1 ;

    ケース6.CASE5.CASE6: (結合テーブルを使ったマッピング)

    さて、one-to-many関係を考えてみましょう。Person(id,name)は複数のVeichle(id,name,number)に関連付けられ、複数の車両は同一人物に属することができます。

    ある日、高速道路のガソリン保安官カルロスが放置車両(おそらく盗難車両)を数台発見しました。 今、保安官はデータベースの車両の詳細(番号、名前)を更新する必要がありますが、主な問題は、それらの車両の所有者がいないため、person_id(foreign key )フィールドは空のままであることです。

    Hibernate は次の SQL 文を実行します。

    Vehicle vehicle1 = new Vehicle("ford", 1); 
    Vehicle vehicle2 = new Vehicle("gmc", 2);
    List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
    vehicles.add(vehicle2);
    entityManager.persist(veichles);

    この戦略により、任意の関係を扱うために列に null 値を置くことが強制されます。

    通常、join table を考えるとき、many-to-many 関係を考えますが、この場合、結合テーブルを使用すると、これらのヌル値を排除できます。

    この手法は、結合テーブルを使用して Branch および Company エンティティ間の関連付けを格納します。

    この例では、車両側(Many Side)に@JoinTableアノテーションを適用しています。

    データベーススキーマがどのように見えるか見てみましょう:

    上記のコードにより、person(id,name)vehicle(id,name)vehicle_person(vehicle_id,person_id) という3つのテーブルが生成されます。 ここでvehicle_personは、個人と車両の両方のエンティティへの外部キー関係を保持します。

    だから保安官が車両の詳細を保存すると、我々は車両テーブルではなくvehicle_personテーブルで外部キー関連を保持しているため、車両テーブルに永続化する必要がないNULL値です。

    Vehicle vehicle1 = new Vehicle("ford", 1);
    Vehicle vehicle2 = new Vehicle("gmc", 2);
    List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
    vehicles.add(vehicle2);
    entityManager.persist(veichles);

    Hibernate は次の SQL 文を実行します。

    insert into vehicle (id, name) values (1, "ford"); insert into vehicle (id, name) values (2, "gmc");id |name|
    -----|----|
    1 |ford|
    2 |benz|
    -----------

    この上記の例は、NULL 値挿入を除去する方法を示します。

    @OneToMany 双方向の関係

    • 2762 関連は、子側の @ManyToOne 関連も必要とします。 ドメイン モデルでは、この関連付けをナビゲートするために 2 つの側面を公開していますが、舞台裏では、リレーショナル データベースはこの関係のために 1 つの外部キーしか持っていません。
    • すべての双方向の関連付けは、1 つの所有側のみ (子側) を持っていなければならず、もう 1 つは逆 (mappedBy) 側と呼ばれています。
    • mappedBy 属性セットで @OneToMany を使用する場合、双方向の関連があり、mappedBy が参照する子側に @ManyToOne 関連を持つ必要があることを意味しています。 この属性により、関連するエンティティを両側から参照できます。

    @OneToMany関連をマッピングする最善の方法は、すべてのエンティティ状態の変更を伝播するために @ManyToOne 側に依存することです。

    If we persist 2 Branch(s)

    Hibernate generates just one SQL statement for each persisted Branch entity:

    insert into company (name, id) values ("company1",1); 
    insert into branch (company_id, name, id) values (1,"branch1",1); insert into branch (company_id, name, id) values (1,"branch2",2);

    If we remove a Branch.Of.See.See.See.See.See.The Branch(S).The Branch(S).The Branch (I).If.If.If.If.If.を参照してください。

    Company company = entityManager.find( Company.class, 1L ); Branch branch = company.getBranches().get(0); company.removeBranches(branch);

    実行される削除 SQL 文は 1 つだけです。

    delete from Branch where id = 1

    したがって、双方向 @OneToMany 関連は、1 対多のデータベース関係をマッピングする最良の方法であり、関連の親側のコレクションが本当に必要な場合です。

    @JoinColumn エンティティアソシエーションまたはエレメントコレクションに参加するための列を指定します。 注釈 @JoinColumn は、このエンティティが関係の所有者であることを示します。

    上記の例では、所有者のEntity Branchは、非所有者のCompanyエンティティへの外部キーを持つcompany_idというJoin列を持っています。

    双方向の関連が形成される場合、アプリケーション開発者は常に双方が同期していることを確認する必要があります。 addBranches()removeBranches() は、子要素 (すなわち Branch) が追加または削除されるたびに両端を同期するユーティリティ・メソッドです。

    単方向の @OneToMany とは異なり、コレクションの持続状態を管理するには、双方向の関連がより効率的です。

    すべての要素の削除は、1 回の更新 (その際、外部キー列は NULL に設定される) のみで、子エンティティのライフサイクルが、その親なしでは子が存在できないように所有している親にバインドされている場合、関連付けに orphan-removal 属性を付け、子から関連性を取り除くと実際の子のテーブル行にも delete 文がトリガーされるようにすることができる。

    NOTE:

    • 上記の双方向の例では、エンティティ/オブジェクト/モデル(つまりJavaクラス)の親と子という用語は、SQLの非勝者/逆者と所有者側にそれぞれ対応しています。

      SQLでは、branchはOwner側、CompanyはNon-woning(Inverse)側です。 N個の支店に対して1個の会社があるので、各支店は所属する会社に対する外部キーを持ちます。これは支店が接続(情報)を「所有」(文字通り含む)していることを意味します。

      @OneToMany双方向関係(結合テーブルによるマッピング)

      CASE 6から@JoinTableで単方向マッピングをしているので、PersonエンティティにmappedBy属性を追加すれば双方向関係になる。

      SUMMARY

      前述のマッピングについて、いくつか注意すべき点があります:

      • @ManyToOne 関連では FetchType.LAZY を使用していますが、これはそうしないと EAGER フェッチに戻ってしまいパフォーマンスが悪くなるためです。
      • 双方向の関連付けは常に両側で更新される必要があるため、親側には addChildremoveChild コンボが含まれます (Parent side Company には addBranchesremoveBranches の 2 つのユーティリティ メソッドが含まれます)。 これらのメソッドは、オブジェクトまたはリレーショナル データ破損の問題を避けるために、常に関連付けの両側を同期することを保証します。
      • 子エンティティの Branch は、equals および hashCode メソッドを実装します。 等質性チェックのために自然識別子に頼ることはできないので、代わりにエンティティ識別子を使用する必要があります。 しかし、すべてのエンティティの状態遷移で等質性が一貫するように、適切に行う必要があります。 removeBranches では等式に依存するため、双方向の関連付けでは子エンティティに対して equals と hashCode を上書きするのがよい習慣です。
      • @OneToMany 関連付けは、それが単方向または双方向であっても、定義上は親関連付けです。 アソシエーションの親側だけが、そのエンティティ状態遷移を子にカスケードすることに意味があります。

コメントを残す

メールアドレスが公開されることはありません。