web-dev-qa-db-ja.com

「コレクションフェッチで指定されたfirstResult / maxResults;メモリに適用しています!」という警告を回避するにはどうすればよいですか。 Hibernateを使用する場合

サーバーログに警告が表示されます"コレクションフェッチで指定されたfirstResult/maxResults;メモリに適用中!"。しかし、すべてが正常に動作しています。しかし、この警告は必要ありません。

私のコードは

public employee find(int id) {
    return (employee) getEntityManager().createQuery(QUERY).setParameter("id", id).getSingleResult();
}

私のクエリは

QUERY = "from employee as emp left join fetch emp.salary left join fetch emp.department where emp.id = :id"
26
Prince

この警告の理由は、フェッチ結合が使用される場合、結果セットの順序は選択されたエンティティのIDによってのみ定義され、フェッチされた結合ではないためです。

このメモリ内のソートが問題を引き起こしている場合は、JOIN FETCHでfirsResult/maxResultsを使用しないでください。

18
Mikko Maunu

この警告を回避するには、呼び出しgetSingleResultgetResultList().get(0)に変更する必要があります

21
Lucas

有効な結果が得られていますが、SQLクエリはすべてのデータをフェッチするため、必要なほど効率的ではありません。

この記事 で説明したように、2つのオプションがあります。

読み取り/書き込みモードでエンティティを取得できる2つのSQLクエリの問題を修正

この問題を修正する最も簡単な方法は、2つのクエリを実行することです。

。最初のクエリは、指定されたフィルタリング基準に一致するルートエンティティ識別子を取得します。 。 2番目のクエリは、以前に抽出されたルートエンティティ識別子を使用して、親エンティティと子エンティティを取得します。

このアプローチは実装が非常に簡単で、次のようになります。

List<Long> postIds = entityManager
.createQuery(
    "select p.id " +
    "from Post p " +
    "where p.title like :titlePattern " +
    "order by p.createdOn", Long.class)
.setParameter(
    "titlePattern",
    "High-Performance Java Persistence %"
)
.setMaxResults(5)
.getResultList();

List<Post> posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.comments " +
    "where p.id in (:postIds)", Post.class)
.setParameter("postIds", postIds)
.setHint(
    QueryHints.HINT_PASS_DISTINCT_THROUGH,
    false
)
.getResultList();

読み取り専用モードでのみエンティティを取得できる1つのSQLクエリの問題を修正

2番目のアプローチは、フィルター条件に一致する親および子エンティティの結果セットに対してSDENSE_RANKを使用し、最初のN個の投稿エントリのみの出力を制限することです。

SQLクエリは次のようになります。

@NamedNativeQuery(
    name = "PostWithCommentByRank",
    query =
        "SELECT * " +
        "FROM (   " +
        "    SELECT *, dense_rank() OVER (ORDER BY \"p.created_on\", \"p.id\") rank " +
        "    FROM (   " +
        "        SELECT p.id AS \"p.id\", " +
        "               p.created_on AS \"p.created_on\", " +
        "               p.title AS \"p.title\", " +
        "               pc.id as \"pc.id\", " +
        "               pc.created_on AS \"pc.created_on\", " +
        "               pc.review AS \"pc.review\", " +
        "               pc.post_id AS \"pc.post_id\" " +
        "        FROM post p  " +
        "        LEFT JOIN post_comment pc ON p.id = pc.post_id " +
        "        WHERE p.title LIKE :titlePattern " +
        "        ORDER BY p.created_on " +
        "    ) p_pc " +
        ") p_pc_r " +
        "WHERE p_pc_r.rank <= :rank ",
    resultSetMapping = "PostWithCommentByRankMapping"
)
@SqlResultSetMapping(
    name = "PostWithCommentByRankMapping",
    entities = {
        @EntityResult(
            entityClass = Post.class,
            fields = {
                @FieldResult(name = "id", column = "p.id"),
                @FieldResult(name = "createdOn", column = "p.created_on"),
                @FieldResult(name = "title", column = "p.title"),
            }
        ),
        @EntityResult(
            entityClass = PostComment.class,
            fields = {
                @FieldResult(name = "id", column = "pc.id"),
                @FieldResult(name = "createdOn", column = "pc.created_on"),
                @FieldResult(name = "review", column = "pc.review"),
                @FieldResult(name = "post", column = "pc.post_id"),
            }
        )
    }
)

@NamedNativeQueryは、指定されたタイトルに一致するすべてのPostエンティティと、関連するPostComment子エンティティを取得します。 DENSE_RANK Window Functionは、各PostおよびPostComment結合レコードにランクを割り当てるために使用されます。これにより、後で取得するPostレコードの量だけをフィルタリングできます。

SqlResultSetMappingは、SQLレベルの列エイリアスと、設定が必要なJPAエンティティプロパティとの間のマッピングを提供します。

これで、PostWithCommentByRank@NamedNativeQuery このような:

List<Post> posts = entityManager
.createNamedQuery("PostWithCommentByRank")
.setParameter(
    "titlePattern",
    "High-Performance Java Persistence %"
)
.setParameter(
    "rank",
    5
)
.unwrap(NativeQuery.class)
.setResultTransformer(
    new DistinctPostResultTransformer(entityManager)
)
.getResultList();

これで、デフォルトでは、PostWithCommentByRankのようなネイティブSQLクエリは同じJDBC行のPostとPostCommentをフェッチするため、両方のエンティティを含むObject []になります。

ただし、表形式のObject[]親子エンティティのツリーへの配列。このため、Hibernate ResultTransformerを使用する必要があります。ResultTransformerの詳細については、 この記事

DistinctPostResultTransformerは次のようになります。

public class DistinctPostResultTransformer
        extends BasicTransformerAdapter {

    private final EntityManager entityManager;

    public DistinctPostResultTransformer(
            EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    public List transformList(
            List list) {

        Map<Serializable, Identifiable> identifiableMap =
            new LinkedHashMap<>(list.size());

        for (Object entityArray : list) {
            if (Object[].class.isAssignableFrom(entityArray.getClass())) {
                Post post = null;
                PostComment comment = null;

                Object[] tuples = (Object[]) entityArray;

                for (Object Tuple : tuples) {
                    if(Tuple instanceof Identifiable) {
                        entityManager.detach(Tuple);

                        if (Tuple instanceof Post) {
                            post = (Post) Tuple;
                        }
                        else if (Tuple instanceof PostComment) {
                            comment = (PostComment) Tuple;
                        }
                        else {
                            throw new UnsupportedOperationException(
                                "Tuple " + Tuple.getClass() + " is not supported!"
                            );
                        }
                    }
                }

                if (post != null) {
                    if (!identifiableMap.containsKey(post.getId())) {
                        identifiableMap.put(post.getId(), post);
                        post.setComments(new ArrayList<>());
                    }
                    if (comment != null) {
                        post.addComment(comment);
                    }
                }
            }
        }
        return new ArrayList<>(identifiableMap.values());
    }
}

DistinctPostResultTransformerは、子コレクションを上書きしており、それをエンティティの状態遷移として伝播したくないため、フェッチされるエンティティをデタッチする必要があります。

post.setComments(new ArrayList<>());

詳細については、 こちらの記事 をご覧ください。

16
Vlad Mihalcea

Empには多くの部門があり、1対多の関係になっていると思います。 Hibernateは、フェッチされた部門レコードを使用して、このクエリの多くの行をフェッチします。したがって、結果セットを実際にメモリにフェッチするまで、結果セットの順序を決定することはできません。したがって、ページネーションはメモリ内で行われます。

Empで部門を取得したくないが、それでも部門に基づいてクエリを実行したい場合は、警告なしで結果を得ることができます(メモリ内で順序付けを行わずに)。そのためには、単に「fetch」句を削除する必要があります。次のようなものです:

QUERY =「従業員から従業員が左に参加emp.salary salが左に参加emp.department dep where emp.id =:id and dep.name = 'testing' and sal.salary> 5000 "

0
Shehan Simen

問題は、デカルト積を取得することです。オフセットは、同じルートIDクラスをまだ使用しているかどうかを確認せずにレコードセットをカットします

0