Comment faire une requête limite en JPQL ou HQL?


239

Dans Hibernate 3, existe-t-il un moyen de faire l'équivalent de la limite MySQL suivante dans HQL?

select * from a_table order by a_table_column desc limit 0, 20;

Je ne veux pas utiliser setMaxResults si possible. Cela était certainement possible dans l'ancienne version de Hibernate / HQL, mais il semble avoir disparu.


2
J'utilise Hibernate-5.0.12. N'est-ce toujours pas disponible? Il serait très difficile d'obtenir un million d'enregistrements environ, puis d'appliquer le filtre setMaxResultsdessus, comme l'a remarqué @Rachel dans la réponse de @skaffman.
Rajeev Ranjan

Réponses:


313

Cela a été publié sur le forum Hibernate il y a quelques années lorsqu'on lui a demandé pourquoi cela fonctionnait dans Hibernate 2 mais pas dans Hibernate 3:

Limit n'a jamais été une clause prise en charge dans HQL. Vous êtes censé utiliser setMaxResults ().

Donc, si cela a fonctionné dans Hibernate 2, il semble que ce soit par hasard, plutôt que par conception. Je pense c'était parce que l'analyseur Hibernate 2 HQL remplacerait les bits de la requête qu'il reconnaissait comme HQL, et laisserait le reste tel qu'il était, afin que vous puissiez vous faufiler dans du SQL natif. Hibernate 3, cependant, a un analyseur AST HQL approprié, et c'est beaucoup moins indulgent.

Je pense que c'est Query.setMaxResults()vraiment votre seule option.


3
Je dirais que l'approche d'Hibernate 3 est plus correcte. Votre utilisation d'Hibernate est censée être indépendante de la base de données, vous devez donc faire ce genre de choses de manière abstraite.
mat

6
Je suis d'accord, mais cela rend la migration est une douleur royale dans le cul lorsque les fonctionnalités sont supprimées comme ça.
skaffman

52
mais avec setMaxResults, la première requête est exécutée, puis sur le jeu de résultats que vous appelez, setMaxResultsce qui prendrait un nombre limité de lignes de résultats du jeu de résultats et l'afficherait à l'utilisateur, dans mon cas, j'ai 3 millions d'enregistrements qui sont interrogés, puis j'appelle setMaxResults pour définir 50 enregistrements mais je ne veux pas faire cela, alors que la requête elle-même je veux interroger 50 enregistrements, y a-t-il un moyen de le faire?
Rachel

2
Ancien poste, je sais. Je suis entièrement d'accord avec Rachel. En utilisant NHibernate (port .Net de Hibernate), j'ai récemment mis à niveau de 2 à 3 et même chose, le top X lance maintenant une erreur d'analyse. Cependant, lorsque j'ai ajouté setMaxResults sur la requête, il a généré un TOP X dans le SQL résultant (à l'aide de MsSql2008Dialect). C'est bon.
Thierry_S

17
@Rachel With setMaxResultshibernate ajoutera la limitpièce à la requête. Il n'obtiendra pas tous les résultats. Vous pouvez vérifier la requête qu'il produit en activant:<property name="show_sql">true</property>
Andrejs

148
 // SQL: SELECT * FROM table LIMIT start, maxRows;

Query q = session.createQuery("FROM table");
q.setFirstResult(start);
q.setMaxResults(maxRows);

6
Je préfère celui-ci car il setFirstResultest en fait mentionné dans cette réponse alors qu'ici et ailleurs, ils disent simplement setMaxResultsceci et setMaxResultscela sans mentionner comment définir le décalage.
demongolem

19

Si vous ne souhaitez pas l'utiliser setMaxResults()sur l' Queryobjet, vous pouvez toujours revenir à l'utilisation de SQL normal.


37
Ce n'est pas vraiment excitant.
stevedbrown

8
Je ne trouve pas HQL excitant non plus. Pourquoi ne pas écrire une vue sur votre serveur de base de données qui applique la limite, puis demander à HQL de regarder cette vue: P
pjp

1
Ce n'est qu'une de ces choses, alors que SQL est beaucoup plus facile que HQL pour chaque requête, la création de vues et l'écriture de SQL natif ne sont généralement pas très utiles pour le refactoring. J'essaye de l'éviter quand je peux. Ce vrai problème réel était que j'avais mal écrit ma requête MySQL et pensais que setMaxResults était bizarre. Ça ne l'était pas.
stevedbrown

et si vous essayez de basculer entre différents fournisseurs de SGBD, la douleur vous attend.
Olgun Kaya

5

Si vous ne souhaitez pas utiliser setMaxResults, vous pouvez également utiliser Query.scroll au lieu de list et récupérer les lignes de votre choix. Utile pour la pagination par exemple.


Merci, la réponse acceptée n'a pas résolu le problème pour moi, car setMaxResults()charge d'abord chaque enregistrement en mémoire, puis crée une sous-liste, qui, lorsqu'il y a des centaines de milliers d'enregistrements ou plus, plante le serveur, car il manque de mémoire. Je pourrais cependant passer d'une requête typée JPA à une requête Hibernate QueryImpl hibernateQuery = query.unwrap(QueryImpl.class)et ensuite je pourrais utiliser la scroll()méthode comme vous l'avez suggéré.
toongeorges

Au moins avec le dialecte Oracle, ce n'est pas vrai (Hibernate utilise la colonne virtuelle ROWNUM). Cela dépend peut-être du pilote. D'autres DB ont la fonction TOP.
Lluis Martinez

Ma requête utilise une extraction de jointure. Cela se traduit par l'avertissement Hibernate "firstResult / maxResults spécifié avec la récupération de collection; application en mémoire". Ainsi, avec une extraction de jointure, Hibernate charge la collection complète en mémoire. La suppression de la jointure n'est pas une option pour des raisons de performances. Lorsque j'utilise ScrollableResults, j'ai plus de contrôle sur les enregistrements qui sont chargés en mémoire. Je ne peux pas charger tous les enregistrements avec un seul ScrollableResults, car cela entraîne également une insuffisance de mémoire. J'expérimente le chargement de plusieurs pages avec différents ScrollableResults. Si cela ne fonctionne pas, j'irai avec SQL.
toongeorges

C'est bizarre, je n'ai jamais rencontré ça. Oui, il est parfois préférable d'utiliser JDBC simple, spécialement pour les processus massifs / par lots.
Lluis Martinez

Les relations @OneToMany sont à l'origine de mes problèmes. Si, d'une manière ou d'une autre, je pouvais exécuter la fonction d'agrégation Oracle LISTAGG dans Hibernate pour concaténer les valeurs multiples en une seule valeur, alors je peux supprimer les jointures et les remplacer par une sous-requête.
toongeorges

2

Vous pouvez facilement utiliser la pagination pour cela.

    @QueryHints({ @QueryHint(name = "org.hibernate.cacheable", value = "true") })
    @Query("select * from a_table order by a_table_column desc")
    List<String> getStringValue(Pageable pageable);

vous devez passer new PageRequest(0, 1)pour récupérer les enregistrements et dans la liste récupérer le premier enregistrement.


2

Puisque c'est une question très courante, j'ai écrit cet article , sur lequel cette réponse est basée.

Les setFirstResultet setMaxResults Queryméthodes

Pour un JPA et Hibernate Query, la setFirstResultméthode est l'équivalent de OFFSET, et la setMaxResultsméthode est l'équivalent de LIMIT:

List<Post> posts = entityManager
.createQuery(
    "select p " +
    "from Post p " +
    "order by p.createdOn ")
.setFirstResult(10)
.setMaxResults(10)
.getResultList();

L' LimitHandlerabstraction

Hibernate LimitHandlerdéfinit la logique de pagination spécifique à la base de données et, comme illustré par le diagramme suivant, Hibernate prend en charge de nombreuses options de pagination spécifiques à la base de données:

Implémentations <code> LimitHandler </code>

Maintenant, selon le système de base de données relationnelle sous-jacent que vous utilisez, la requête JPQL ci-dessus utilisera la syntaxe de pagination appropriée.

MySQL

SELECT p.id AS id1_0_,
       p.created_on AS created_2_0_,
       p.title AS title3_0_
FROM post p
ORDER BY p.created_on
LIMIT ?, ?

PostgreSQL

SELECT p.id AS id1_0_,
       p.created_on AS created_2_0_,
       p.title AS title3_0_
FROM post p
ORDER BY p.created_on
LIMIT ?
OFFSET ?

serveur SQL

SELECT p.id AS id1_0_,
       p.created_on AS created_on2_0_,
       p.title AS title3_0_
FROM post p
ORDER BY p.created_on
OFFSET ? ROWS 
FETCH NEXT ? ROWS ONLY

Oracle

SELECT *
FROM (
    SELECT 
        row_.*, rownum rownum_
    FROM (
        SELECT 
            p.id AS id1_0_,
            p.created_on AS created_on2_0_,
            p.title AS title3_0_
        FROM post p
        ORDER BY p.created_on
    ) row_
    WHERE rownum <= ?
)
WHERE rownum_ > ?

L'avantage d'utiliser setFirstResultetsetMaxResults est qu'Hibernate peut générer la syntaxe de pagination spécifique à la base de données pour toutes les bases de données relationnelles prises en charge.

Et, vous n'êtes pas limité aux requêtes JPQL uniquement. Vous pouvez utiliser la méthode setFirstResultet setMaxResultssept pour les requêtes SQL natives.

Requêtes SQL natives

Vous n'avez pas à coder en dur la pagination spécifique à la base de données lorsque vous utilisez des requêtes SQL natives. Hibernate peut ajouter cela à vos requêtes.

Donc, si vous exécutez cette requête SQL sur PostgreSQL:

List<Tuple> posts = entityManager
.createNativeQuery(
    "SELECT " +
    "   p.id AS id, " +
    "   p.title AS title " +
    "from post p " +
    "ORDER BY p.created_on", Tuple.class)
.setFirstResult(10)
.setMaxResults(10)
.getResultList();

Hibernate le transformera comme suit:

SELECT p.id AS id,
       p.title AS title
FROM post p
ORDER BY p.created_on
LIMIT ?
OFFSET ?

Cool, non?

Au-delà de la pagination basée sur SQL

La pagination est bonne lorsque vous pouvez indexer les critères de filtrage et de tri. Si vos exigences de pagination impliquent un filtrage dynamique, c'est une bien meilleure approche que d'utiliser une solution à index inversé, comme ElasticSearch.

Consultez cet article pour plus de détails.


1

String hql = "select userName from AccountInfo order by points desc 5";

Cela a fonctionné pour moi sans utiliser setmaxResults();

Fournissez simplement la valeur maximale dans le dernier (dans ce cas 5) sans utiliser le mot clé limit. : P


7
Hm ... pas trop sûr de cela, [la citation nécessaire] cela peut être un accident et pourrait soudainement cesser de fonctionner dans une nouvelle version de hibernate.
Konrad 'ktoso' Malawski

4
Veuillez vous référer au document officiel.
Do Nhu Vy

1
Cela ne fonctionne pas pour moi dans Hibernate Version 5.2.12 Final
jDub9

1
Cela ne fonctionne pas, ne prend pas également en charge la mise en veille prolongée 4.x.
Faiz Akram

0

Mon observation est que même si vous avez une limite dans le HQL (hibernate 3.x), cela provoquera une erreur d'analyse ou sera simplement ignoré. (si vous avez commandé par + desc / asc avant la limite, il sera ignoré, si vous n'avez pas desc / asc avant la limite, cela provoquera une erreur d'analyse)


0

Si peut gérer une limite dans ce mode

public List<ExampleModel> listExampleModel() {
    return listExampleModel(null, null);
}

public List<ExampleModel> listExampleModel(Integer first, Integer count) {
    Query tmp = getSession().createQuery("from ExampleModel");

    if (first != null)
        tmp.setFirstResult(first);
    if (count != null)
        tmp.setMaxResults(count);

    return (List<ExampleModel>)tmp.list();
}

Il s'agit d'un code très simple pour gérer une limite ou une liste.


0
Criteria criteria=curdSession.createCriteria(DTOCLASS.class).addOrder(Order.desc("feild_name"));
                criteria.setMaxResults(3);
                List<DTOCLASS> users = (List<DTOCLASS>) criteria.list();
for (DTOCLASS user : users) {
                System.out.println(user.getStart());
            }

0

Vous devez écrire une requête native, référez - vous à ceci .

@Query(value =
    "SELECT * FROM user_metric UM WHERE UM.user_id = :userId AND UM.metric_id = :metricId LIMIT :limit", nativeQuery = true)
List<UserMetricValue> findTopNByUserIdAndMetricId(
    @Param("userId") String userId, @Param("metricId") Long metricId,
    @Param("limit") int limit);

0

Vous pouvez utiliser la requête ci-dessous

NativeQuery<Object[]> query = session.createNativeQuery(select * from employee limit ?)
query.setparameter(1,1);

0

L'extrait ci-dessous est utilisé pour effectuer une requête de limite à l'aide de HQL.

Query query = session.createQuery("....");
query.setFirstResult(startPosition);
query.setMaxResults(maxRows);

Vous pouvez obtenir une application de démonstration sur ce lien .


-1
@Query(nativeQuery = true,
       value = "select from otp u where u.email =:email order by u.dateTime desc limit 1")
public List<otp> findOtp(@Param("email") String email);
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.