Créer une requête Hibernate avec l’API Criteria devant retourner un ensemble d’éléments uniques en tenant compte d’une pagination et des tris, n’est pas si aisé. D’ailleurs la FAQ d’Hibernate nous le confirme lorsqu’on lit ceci :
- Hibernate does not return distinct results using for a query with outer join fetching enabled for a collection
- One day Hibernate might be smart enough to know that if you call setFirstResult() or setMaxResults() it should not use a join, but a second SQL SELECT. Try it, your version of Hibernate might already be smart enough. If not, write two queries, one for limiting stuff, the other for eager fetching

© http://www.packtpub.com
Je vais vous exposer la solution que j’utilise depuis peu qui permet de n’effectuer que deux requêtes en base.
Problème à résoudre
Imaginons la requête suivante qui doit récupérer les 25 decks uniques à partir du 50ème dont la saison est le numéro 502, le tout trié par nom de deck. Instinctivement voici la requête Criteria qui nous vient à l’esprit :
Criteria criteria = getSession().createCriteria(Deck.class);
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
criteria.createAlias("season", "dbSeason");
criteria.add(Restrictions.in("dbSeason.pkSeason", new Integer[] { 502 }));
criteria.setFirstResult(50);
criteria.setMaxResults(25);
criteria.addOrder(Order.asc("name"));
return criteria.list();
Malheureusement ceci ne va retourner les decks attendus, les méthodes setFirstResult et setMaxResults ne sont pas compatibles avec la méthode setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).
Voici donc un moyen de contourner ceci.
Solution
La solution consiste à appliquer à la lettre la FAQ d’Hibernate citée plus haut, construire une requête en deux temps :
- la requête principale va se contenter de récupérer uniquement les identifiants de decks correspondant aux critères en tenant compte de la pagination et des tris,
- la sous-requête va récupérer l’intégralité des decks en utilisant la restriction in. Ne pas oublier d’appliquer à nouveau les tris car sinon l’ordre des decks récupérés n’est pas conservé.
Criteria searchCriteria = getSession().createCriteria(Deck.class);
searchCriteria.setProjection(Projections.distinct(Projections.id()));
searchCriteria.createAlias("season", "dbSeason");
searchCriteria.add(Restrictions.in("dbSeason.pkSeason", new Integer[] { 502 }));
searchCriteria.setFirstResult(50);
searchCriteria.setMaxResults(25);
searchCriteria.addOrder(Order.asc("name"));
List<Integer> decksIdList = searchCriteria.list();
List<Deck> decksList = new ArrayList<Deck>();
if (!decksIdList.isEmpty()) {
Criteria selectCriteria = getSession().createCriteria(Deck.class);
selectCriteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
selectCriteria.add(Restrictions.in("pkDeck", decksIdList));
selectCriteria.addOrder(Order.asc("name"));
decksList = selectCriteria.list();
}
return decksList;
Peut faire mieux ?
Je n’ai pas réussi à tout faire en une seule requête en utilisant SubCriteria et la restriction SubQueries.propertyIn car les méthodes setMaxResults et setFirstResult ne sont pas applicable à celle-ci.
Si quelqu’une a réussi ou à une meilleure solution, ça m’intéresse ![]()



Affichez votre portrait
{ 2 commentaires… à vous de vous exprimer ! }
Espérons que les gens pourront te venir en aide
Je suis bien déçu de ne pas avoir eu un autre point de vue sur ce sujet …