多対多の関連付けは、JPAおよびHibernateで最も一般的に使用される関連付けの一つです。 あなたは現実の世界でそれらのための多くの例を見つけることができます、そしてあなたはあなたのドメインモデルの単方向または双方向の関連
しかし、おそらくこれらのマッピングがいくつかの落とし穴を提供することも知っています。 この記事では、これらの落とし穴を回避し、効率的なマッピングを実装するのに役立つ5つのベストプラクティスを紹介します。 あなたは学びます:
- あなたのアソシエーションのための最も効率的なデータ型
- あなたのアソシエーションを管理するためのユーティリティメソッドが必要な理由
- 効率的なマッピングのための正しいFetchType
- クエリ固有のフェッチを使用するときと方法
- すべてのコストで避けるべきCascadeType
基本的な多対多マッピングの詳細については説明しません。 このようなマッピングを作成する方法が正確にわからない場合は、私の関連マッピングガイドの多対多のセクションを見てください。
あなたの関連付けのための最も効率的なデータ型
ほとんどの開発者は、多対多の関連付けのデータ型に多くの考えを費やすことはありません。 彼らはちょうどjavaを選択します。ユーティルそれは単純だと重複を避けるために、任意のチェックを実行しないため、リストします。基本的なJavaクラスを実装する場合、または1対多/多対一の関連付けをモデル化する場合は、これで問題ありません。 しかし、多対多の関連付けをモデル化する場合は、決してリストを使用するべきではありません。
@Entitypublic class Book {// DON'T DO THIS!!!@ManyToMany@JoinTable(name = "book_author", joinColumns = { @JoinColumn(name = "fk_book") }, inverseJoinColumns = { @JoinColumn(name = "fk_author") })private List<Author> authors = new ArrayList<Author>();...}
Hibernateは、javaにマップされた多対多の関係に対する削除操作を処理します。ユーティルリストは非常に非効率的に。
em = emf.createEntityManager();em.getTransaction().begin();// Get Book entity with 2 Authorsb = em.find(Book.class, 1L);// Remove one of the Authorb.getAuthors().remove(a);em.getTransaction().commit();em.close();
最初に関連テーブルからすべてのレコードを削除してから、残りのすべてのレコードを挿入します。代わりに、多対多の関連付けをjavaとしてモデル化する必要があります。ユーティルセット。
@Entitypublic class Book {@ManyToMany@JoinTable(name = "book_author", joinColumns = { @JoinColumn(name = "fk_book") }, inverseJoinColumns = { @JoinColumn(name = "fk_author") })private Set<Author> authors = new HashSet<Author>();...}
Hibernateは、関連付けの削除操作をはるかに優れて処理します。 これで、関連付けから期待されるレコードのみが削除され、他のレコードはそのまま保持されます。
10:00:37,709 DEBUG - update Book set title=?, version=? where id=? and version=?10:00:37,711 DEBUG - delete from book_author where fk_book=? and fk_author=?
関連付けを管理するためのユーティリティメソッドが必要な理由
双方向の関連付けは、関係の両端のエンティティ属性にマッ したがって、前の例では、Bookエンティティにauthors属性があり、Authorエンティティにbooks属性があります。 これらの属性を使用してJOIN句を定義できるため、JPQLまたはCriteriaQueryの実装が非常に快適になります。しかし、関連付けを追加または削除すると、より複雑になります。
しかし、関連付けを追加または削除すると、 あなたは常に関連付けの両端で変更を実行する必要があります。 たとえば、書籍をAuthorに追加する場合は、その書籍をAuthorエンティティのbooks属性に追加し、Bookエンティティのauthorにauthors属性を追加する必要があります。 それ以外の場合、現在の永続コンテキストには、現在のトランザクションが終了するまで使用する一貫性のないデータが含まれています。
Book b = new Book();b.setTitle("Hibernate Tips - More than 70 solutions to common Hibernate problems");em.persist(b);Author a = em.find(Author.class, 1L);a.getBooks().add(b);b.getAuthors().add(a);
AuthorエンティティとBookエンティティのユーティリティメソッドは、更新と削除をはるかに簡単にします。 これらのメソッド内では、両方のエンティティに対して必要な操作を実行します。p>
@Entitypublic class Author {@ManyToMany(mappedBy = "authors")private Set<Book> books = new HashSet<Book>();...public void addBook(Book book) {this.books.add(book);book.getAuthors().add(this);}public void removeBook(Book book) {this.books.remove(book);book.getAuthors().remove(this);}}
効率的なマッピングのための右のFetchType
これは簡単なものです。 常にFetchTypeを使用する必要があります。あなたの多対多の団体のために怠惰。 永続化プロバイダに、使用するまで、関連するエンティティをデータベースから取得しないように指示します。 これは、通常、getterメソッドを初めて呼び出す場合に当てはまります。
幸いなことに、それはすべての対多の関連付けのデフォルトです。 だから、あなたがそれを変更しないことを確認してください。また、JPAのさまざまなFetchTypesについてもっと知りたい場合は、jpa FetchTypesの紹介をご覧ください。fetchtypeを使用している場合は、クエリ固有のフェッチを使用するタイミングと方法
。LAZY、クエリ固有のフェッチについて知っておく必要があります。 それ以外の場合は、n+1の選択問題がたくさん作成されたため、アプリケーションは非常に遅くなります。
エンティティをロードし、クエリ固有のフェッチを使用すると、フェッチされたエンティティごとに 次に、クエリのSELECT句を拡張して、これらの他のエンティティによってマップされた列を含め、関連付けを初期化します。 また、関連付けはすでに初期化されているため、Hibernateはgetterメソッドに初めてアクセスするときに追加のクエリを実行する必要はありません。
クエリ固有のフェッチは、いくつかの異なる方法で実装できます。 最も簡単なのはJOIN FETCH句です。 ただし、以前の記事で説明した@NamedEntityGraphまたはEntityGraphを使用することもできます。JOIN FETCH句の定義は、JPQL問合せの単純なJOIN句とほぼ同じです。 あなただけのFETCHキーワードを追加する必要があります。たとえそうであっても、JOINとJOIN FETCH句は非常に似ていますが、JOIN FETCH句は生成されたSQLクエリにはるかに大きな影響を与えます。 JPQL JOIN句の場合と同様に、sql JOINに変換されるだけでなく、永続化プロバイダに、関連するエンティティによってマップされたすべての列によってSELECT句を拡張するよう強制します。
16:21:03,046 DEBUG SQL:94 - select author0_.id as id1_0_0_, book2_.id as id1_1_1_, author0_.firstName as firstNam2_0_0_, author0_.lastName as lastName3_0_0_, author0_.version as version4_0_0_, book2_.format as format2_1_1_, book2_.publishingDate as publishi3_1_1_, book2_.title as title4_1_1_, book2_.version as version5_1_1_, books1_.author_id as author_i2_2_0__, books1_.book_id as book_id1_2_0__ from Author author0_ inner join book_author books1_ on author0_.id=books1_.author_id inner join Book book2_ on books1_.book_id=book2_.id where author0_.id=1
すべてのコストで避けるべきCascadeType
関連付けでカスケードを有効にすると、永続化プロバイダはエンティティに対して実行する操作を関連するすべてのエンティティに適用します。 すべての操作に対して、または選択されたいくつかの操作に対してそれを行う場合は、構成されたCascadeTypeに依存します。
これは、ビジネスロジックの実装をはるかに簡単にする素晴ら そして、それは完全に間違っていません。しかし、多対多の関連付けのために、Removeを含むCascadeTypes REMOVEとALLは避けてください。 最良のケースでは、パフォーマンスの問題のみが発生しますが、最悪のケースでは、意図したよりも多くのレコードが削除される可能性もあります。私は前の記事で大きな詳細に落とし穴とその解決策の両方を説明しました。
私は前の記事で落とし穴とその解決策の両方を説明しました。 または、それを単純にしたい場合は、関連するエンティティで必要な情報をプログラムでトリガーします。 これにはさらに数行のコードが必要になるかもしれませんが、予期しない副作用は回避されます。あなたは現実の世界で多対多の関連付けのための多くの例を見つけることができ、あなたは簡単にJPAとHibernateでそれらをマップすることができます。
結論
あなたは簡単にそれらをマップすることができます。 残念ながら、これらの単純なマッピングは、次の5つのベストプラクティスに従うことで避けることができるいくつかの落とし穴を隠します。
- ユーティルセット。
- 関連付けからエンティティを追加または削除するユーティリティメソッドを提供します。
- 常にFetchTypeを使用します。パフォーマンスの問題を回避するために、デフォルトであるLAZY。
- クエリ固有のフェッチを適用して、n+1の選択の問題を回避します。
- CascadeTypes REMOVEとALLを使用しないでください。