web-dev-qa-db-fra.com

Comment éviter l'avertissement "firstResult/maxResults spécifié avec la récupération de collection; application en mémoire!" quand vous utilisez Hibernate?

Je reçois un avertissement dans le journal du serveur "firstResult/maxResults spécifié avec la récupération de la collection; application en mémoire!" . Cependant tout fonctionne bien. Mais je ne veux pas cet avertissement.

Mon code est

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

Ma requête est

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

La raison de cet avertissement est que, lorsque la jointure d'extraction est utilisée, l'ordre dans les ensembles de résultats est défini uniquement par l'ID de l'entité sélectionnée (et non par l'extraction de jointure).

Si ce tri en mémoire pose des problèmes, n'utilisez pas firsResult/maxResults avec JOIN FETCH.

14
Mikko Maunu

Pour éviter cela, vous devez changer l'appel getSingleResult en getResultList().get(0)

20
Lucas

Bien que vous obteniez des résultats valides, la requête SQL récupère toutes les données et n'est pas aussi efficace qu'elle le devrait.

Comme je l'ai expliqué dans cet article , vous avez deux options.

Résolution du problème avec deux requêtes SQL pouvant extraire des entités en mode lecture-écriture

Le moyen le plus simple de résoudre ce problème consiste à exécuter deux requêtes:

. La première requête va récupérer les identifiants d'entité racine correspondant aux critères de filtrage fournis .. La seconde requête utilisera les identifiants d'entité racine précédemment extraits pour extraire les entités parent et enfant.

Cette approche est très facile à mettre en œuvre et se présente comme suit:

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();

Résolution du problème avec une requête SQL pouvant extraire uniquement des entités en mode lecture seule

La deuxième approche consiste à utiliser SDENSE_RANK sur l'ensemble de résultats des entités parent et enfant qui correspondent à nos critères de filtrage et à limiter la sortie pour les N premières entrées uniquement.

La requête SQL peut ressembler à ceci:

@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"),
            }
        )
    }
)

Le @NamedNativeQuery extrait toutes les entités post correspondant au titre fourni ainsi que leurs entités enfants PostComment associées. La fonction de fenêtre DENSE_RANK est utilisée pour attribuer le rang de chaque enregistrement joint à une publication et à une PostComment afin de pouvoir filtrer ultérieurement uniquement le nombre d'enregistrements de publication que nous souhaitons récupérer.

SqlResultSetMapping fournit le mappage entre les alias de colonne de niveau SQL et les propriétés d'entité JPA à renseigner.

Maintenant, nous pouvons exécuter la PostWithCommentByRank@NamedNativeQuery comme ceci:

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

Désormais, par défaut, une requête SQL native telle que PostWithCommentByRank devrait extraire Post et PostComment dans la même ligne JDBC, nous allons donc nous retrouver avec un objet [] contenant les deux entités.

Cependant, nous voulons transformer le tableau tabulaire Object[] en une arborescence d'entités parent-enfant et pour cette raison, nous devons utiliser Hibernate ResultTransformer. Pour plus de détails sur le ResultTransformer, consultez cet article .

La DistinctPostResultTransformer se présente comme suit:

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());
    }
}

La DistinctPostResultTransformer doit détacher les entités à extraire, car nous remplaçons la collection enfant et nous ne souhaitons pas que cela soit propagé en tant que transition d'état d'entité:

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

Pour plus de détails, consultez cet article .

9
Vlad Mihalcea

Je suppose que le ministère a de nombreux départements, ce qui est une relation un à plusieurs. Hibernate récupérera de nombreuses lignes pour cette requête avec les enregistrements de service récupérés. Donc, l'ordre des résultats ne peut pas être décidé avant d'avoir récupéré les résultats dans la mémoire. Donc, la pagination sera faite en mémoire.

Si vous ne voulez pas aller chercher les départements avec emp, mais voulez quand même faire une requête basée sur le département, vous pouvez obtenir le résultat sans avertissement (sans faire de commande en mémoire). Pour cela, vous devez simplement supprimer la clause "fetch". Donc, quelque chose comme ceci:

QUERY = "de l'employé en tant que poste gauche rejoint emp.salary sal part gauche de emp.department dep où emp.id =: id et dep.name = 'testing' et sal.salary> 5000"

0
Shehan Simen

le problème est que vous obtiendrez un produit cartésien avec JOIN. Le décalage coupera votre jeu d'enregistrements sans regarder si vous êtes toujours sur la même classe d'identité racine 

0
Benjamin Fuentes