15

エンティティを積極的にロードするためのオプションをリポジトリ レイヤーに追加したいので、すべての関係を含む質問エンティティを積極的にロードするメソッドを追加しようとしましたが、MultipleBagFetchException がスローされます。どうすればこれを修正できますか? 休止状態 4.16 を使用しています。

@NamedQuery(name = Question.FIND_BY_ID_EAGER, query = "SELECT q FROM Question q LEFT JOIN FETCH q.answers LEFT JOIN FETCH q.categories LEFT JOIN FETCH q.feedback LEFT JOIN FETCH q.participant WHERE q.id = :id"),

最初は遅延ロードされた質問オブジェクトを取得して、すべてのリレーションを熱心にロードするにはどうすればよいですか?

4

2 に答える 2

37

これは、Hibernate および実際には一般的な ORM ではかなり厄介な問題です。

何が起こるかというと、多数の (フェッチ) 結合により、かなり大きなデカルト積が作成されます。つまり、他の結合の新しい列と新しい行が結果に表示され、(かなり)大きな「正方形」の結果が得られます。

Hibernate はこのテーブルからグラフを抽出する必要がありますが、適切な列を適切なエンティティに一致させるほどスマートではありません。

例えば

結果が得られたとします。

A B C
A B D

次のようになる必要があります。

 A
 |
 B
 /\
C  D

Hibernate は主キーといくつかのエンコーディング マジックから、グラフがどうあるべきかを推測できますが、実際には、これを実行するには明示的な助けが必要です。

これを行う 1 つの方法は、リレーションでHibernate 固有@IndexColumnまたは JPA 標準を指定することです。@OrderColumn

例えば

@Entity
public class Question {


    @ManyToMany
    @JoinTable(
        name = "question_to_answer",
        joinColumns = @JoinColumn(name = "question_id"),
        inverseJoinColumns = @JoinColumn(name = "answer_id")
    )
    @IndexColumn(name = "answer_order")
    private List<Answer> answers;

    // ...
}

この例では、追加の列を含む結合テーブルを使用していますanswer_order。質問/回答の関係ごとに一意の連続番号を持つこの列を介して、Hibernate は結果テーブルのエントリを識別し、必要なオブジェクト グラフを作成できます。

ところで、それが複数のエンティティに関係する場合、非常に多くの熱心な結合を使用すると、関係するエンティティの数に基づいて考えられるよりもはるかに大きな結果セットになる可能性があります。

参考文献:

于 2012-11-11T20:30:34.823 に答える
6

Hibernate は複数のバッグをフェッチすることを許可しません。これはデカルト積を生成するためです。また、Hibernate 用語でバッグと呼ばれる順序付けられていないリストの場合、基になるコレクションにそれらの重複した行がなくても、重複するエントリが発生します。そのため、Hibernate は、JPQL クエリがコンパイルされるときに、この状況を単純に防ぎます。

Setこれで、コレクションに の代わりにを使用するように指示する多くの回答、ブログ投稿、ビデオ、またはその他のリソースが見つかりますList

それはひどいアドバイスです。そうしないでください!

Setsの代わりに使用ListsするとMultipleBagFetchException消えますが、デカルト積は引き続き存在します。

正しい修正

JOIN FETCH単一の JPQL または Criteria API クエリで複数を使用する代わりに:

List<Post> posts = entityManager
.createQuery(
    "select p " +
    "from Post p " +
    "left join fetch p.comments " +
    "left join fetch p.tags " +
    "where p.id between :minId and :maxId", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();

次のトリックを実行できます。

List<Post> posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.comments " +
    "where p.id between :minId and :maxId ", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.tags t " +
    "where p in :posts ", Post.class)
.setParameter("posts", posts)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

を使用して最大で 1 つのコレクションをフェッチする限り、JOIN FETCH問題ありません。複数のクエリを使用することで、他のコレクションではなく最初のコレクションがセカンダリ クエリを使用してフェッチされるため、デカルト積を回避できます。

于 2019-11-12T19:12:54.423 に答える