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.
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"
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.
Pour éviter cela, vous devez changer l'appel getSingleResult
en getResultList().get(0)
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.
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();
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 .
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"
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