Как сделать запрос на ограничение в JPQL или HQL?

Есть ли в Hibernate 3 способ выполнить эквивалент следующего ограничения MySQL в HQL?

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

Я не хочу использовать setMaxResults, если это возможно. Это определенно было возможно в более старой версии Hibernate / HQL, но, похоже, исчезло.


person stevedbrown    schedule 06.08.2009    source источник
comment
Я использую Hibernate-5.0.12. Это все еще недоступно? Было бы очень сложно получить миллион или около того записей, а затем применить к ним фильтр - setMaxResults поверх него, как заметил @Rachel в ответе @skaffman.   -  person Rajeev Ranjan    schedule 23.10.2017


Ответы (12)


Это было размещено на форуме Hibernate несколько лет назад, когда его спросили, почему это работало в Hibernate 2, но не в Hibernate 3:

Ограничение никогда не поддерживалось в HQL. Вы должны использовать setMaxResults ().

Так что, если это сработало в Hibernate 2, похоже, что это было случайно, а не по замыслу. Я думаю, что это произошло из-за того, что парсер Hibernate 2 HQL заменил бы те биты запроса, которые он распознал как HQL, и оставил бы все остальное как есть, чтобы вы могли незаметно использовать некоторый родной SQL. Однако в Hibernate 3 есть правильный AST HQL Parser, и он гораздо менее снисходительный.

Я думаю, Query.setMaxResults() действительно ваш единственный вариант.

person skaffman    schedule 06.08.2009
comment
Я как-то надеялся, что они поняли, что это меня раздражает. Кроме того, если вы посмотрели на исходный код, они реализовали его для каждой базы данных, и каждая база данных отличается. Ну что ж, это грустно. - person stevedbrown; 06.08.2009
comment
Я бы сказал, что подход Hibernate 3 более правильный. Предполагается, что вы используете Hibernate без привязки к базе данных, поэтому вам придется делать подобные вещи абстрактно. - person matt b; 06.08.2009
comment
Я согласен, но это делает миграцию королевской головной болью, когда такие функции отбрасываются. - person skaffman; 06.08.2009
comment
но с setMaxResults сначала выполняется запрос, а затем в наборе результатов вы вызываете setMaxResults, который будет принимать ограниченное количество строк результатов из набора результатов и отображать его пользователю, в моем случае у меня есть 3 миллиона записей, которые запрашиваются, а затем я вызываю setMaxResults для установить 50 записей, но я не хочу этого делать, в то время как сам запрос я хочу запросить 50 записей, есть ли способ сделать это? - person Rachel; 28.01.2012
comment
Старый пост знаю. Я полностью согласен с Рэйчел. Используя NHibernate (порт .Net для Hibernate), я недавно обновился с 2 до 3 и то же самое, top X теперь выдает ошибку парсера. Однако, когда я добавил в запрос setMaxResults, он сгенерировал TOP X в результирующем SQL (с использованием MsSql2008Dialect). Это хорошо. - person Thierry_S; 04.11.2013
comment
@Rachel With setMaxResults hibernate добавит в запрос часть limit. Это не даст всех результатов. Вы можете проверить создаваемый запрос, включив: <property name="show_sql">true</property> - person Andrejs; 23.12.2014
comment
Имеет смысл только то, что эта функция поддерживается не всеми базами данных. Например: SQL Oracle не имеет ключевого слова limit или top, хотя у него есть rownum, но вы не можете использовать его в сочетании с сортировкой, если не оберните его (т.е. не используйте подзапросы). Но из-за этого трудно совместить его с join. MySQL и Teradata используют top и требуют, чтобы он находился прямо перед запросом, но mysql требует, чтобы он был полностью позади с использованием ключевого слова limit. Нетрудно поверить, что MySQL и SQL Server поддерживают это, но я был бы удивлен, если бы это сделал Oracle. - person bvdb; 24.07.2015
comment
Я не думаю, что это не сработало в спящем режиме 3. В jboss 5.1.0.GA, который включал лимит Hibernate 3.3.1, работал нормально. - person Marinos An; 07.03.2017
comment
@Andrejs, когда запрос использует соединения выборки, оказывается, что setMaxResults() не добавляет limit к запросу (для Oracle DB), но загружает все записи в память и уменьшает количество записей там. Когда у вас есть миллионы совпадающих записей, это означает OutOfMemoryError (или связанную ошибку) - person toongeorges; 20.06.2017
comment
Я использую Hibernate-5.0.12. Это все еще недоступно? Было бы очень сложно получить миллион или около того записей, а затем применить к ним фильтр it- setMaxResults, как заметил @Rachel. - person Rajeev Ranjan; 23.10.2017
comment
Взгляните на эту страницу, которая объясняет обоснование этого решения: dzone.com/articles/ hibernate-tuning-query-using В частности, ключевое слово LIMIT не зависит от базы данных, поэтому setMaxResults необходим для преобразования запроса в соответствующее ключевое слово на вашем диалекте. Во-вторых, если запрос включает FETCH JOIN, ключевое слово LIMIT не будет иметь смысла, поскольку оно будет применяться к строкам, а не к родительским объектам. Следовательно, Hibernate не включает его. Получение небольшого фрагмента данных из многих больших таблиц по-прежнему является проблемой, но, надеюсь, это прольет свет на ограничения Hibernate здесь. - person Mattias Martens; 01.12.2017

Если вы не хотите использовать setMaxResults() в объекте Query, вы всегда можете вернуться к использованию обычного SQL.

person pjp    schedule 06.08.2009
comment
На самом деле это не так уж и интересно. - person stevedbrown; 06.08.2009
comment
Я тоже не считаю HQL захватывающим. Почему бы не написать представление на вашем сервере БД, которое применяет ограничение, а затем заставить HQL посмотреть на это представление: P - person pjp; 06.08.2009
comment
Это просто одна из тех вещей, хотя SQL намного проще, чем HQL для каждого запроса, создание представлений и написание собственного SQL, как правило, не так хороши для рефакторинга. Я стараюсь избегать этого, когда могу. Настоящая проблема заключалась в том, что я все равно неправильно написал свой MySQL-запрос и подумал, что setMaxResults был странным. Это было не так. - person stevedbrown; 06.08.2009
comment
и если вы попытаетесь переключиться между разными поставщиками СУБД, вас ждут боли. - person Olgun Kaya; 06.11.2018
comment
вместо использования limit я использовал pageable, чтобы избежать лишних данных - person mohamed amine salah; 25.01.2021

Если вы не хотите использовать setMaxResults, вы также можете использовать Query.scroll вместо list и получать нужные строки. Полезно, например, для пейджинга.

person Lluis Martinez    schedule 08.12.2009
comment
Спасибо, принятый ответ не решил проблему для меня, потому что setMaxResults() сначала загружает каждую запись в память, а затем создает подсписок, который, когда есть сотни тысяч или более записей, приводит к сбою сервера, потому что ему не хватает памяти. Однако я мог бы перейти от типизированного запроса JPA к запросу Hibernate через QueryImpl hibernateQuery = query.unwrap(QueryImpl.class), а затем я мог бы использовать метод scroll(), как вы предложили. - person toongeorges; 21.06.2017
comment
По крайней мере, с диалектом Oracle это неверно (Hibernate использует виртуальный столбец ROWNUM). Может это зависит от драйвера. Остальные БД имеют функцию TOP. - person Lluis Martinez; 22.06.2017
comment
В моем запросе используется объединенная выборка. Это приводит к предупреждению Hibernate firstResult / maxResults, указанному с помощью выборки коллекции; применение в памяти. Таким образом, при объединении выборки Hibernate загружает полную коллекцию в память. Отказ от соединения невозможен по причинам производительности. Когда я использую ScrollableResults, у меня больше контроля над тем, какие записи загружаются в память. Я не могу загрузить все записи с помощью одного ScrollableResults, потому что это также приводит к нехватке памяти. Я экспериментирую с загрузкой нескольких страниц с разными ScrollableResults. Если это не сработает, я воспользуюсь SQL. - person toongeorges; 22.06.2017
comment
Странно, никогда с таким не сталкивался. Да, иногда использование прямого JDBC - лучший способ, особенно для массовых / пакетных процессов. - person Lluis Martinez; 22.06.2017
comment
Отношения @OneToMany вызывают у меня проблемы. Если бы я каким-то образом мог выполнить агрегатную функцию Oracle LISTAGG в Hibernate для объединения нескольких значений в одно значение, тогда я мог бы отбросить объединения и заменить их подзапросом. - person toongeorges; 22.06.2017
comment
Я добавил несколько новых полей в свою сущность, которые я аннотировал с помощью @Formula. С помощью формулы я вызываю функцию Oracle LISTAGG. Поэтому мне больше не нужно присоединяться к получению отношений @OneToMany. Без объединенной выборки я могу разбить свой запрос JPA на страницу с setMaxResults(). - person toongeorges; 22.06.2017
comment
Только если ваше приложение - Oracle. - person Lluis Martinez; 23.06.2017

Методы setFirstResult и setMaxResults Query

Для JPA и Hibernate Query метод setFirstResult эквивалентен OFFSET, а метод setMaxResults эквивалентен LIMIT:

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

LimitHandler абстракция

Hibernate LimitHandler определяет логику разбиения на страницы для конкретной базы данных, и, как показано на следующей диаграмме, Hibernate поддерживает множество параметров разбивки на страницы для конкретной базы данных:

Реализации LimitHandler

Теперь, в зависимости от используемой базовой системы реляционной базы данных, в приведенном выше запросе JPQL будет использоваться правильный синтаксис разбивки на страницы.

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 ?

SQL Server

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_ > ?

Преимущество использования setFirstResult и setMaxResults заключается в том, что Hibernate может генерировать специфичный для базы данных синтаксис разбивки на страницы для любых поддерживаемых реляционных баз данных.

И вы не ограничены только запросами JPQL. Вы можете использовать седьмой метод setFirstResult и setMaxResults для собственных запросов SQL.

Собственные запросы SQL

Вам не нужно жестко кодировать разбиение на страницы для конкретной базы данных при использовании собственных SQL-запросов. Hibernate может добавить это к вашим запросам.

Итак, если вы выполняете этот SQL-запрос в 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 преобразует его следующим образом:

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

Круто, правда?

Помимо разбивки на страницы на основе SQL

Разбивка на страницы хороша, когда вы можете индексировать критерии фильтрации и сортировки. Если ваши требования к разбивке на страницы подразумевают динамическую фильтрацию, гораздо лучше использовать решение с инвертированным индексом, такое как ElasticSearch.

person Vlad Mihalcea    schedule 25.02.2020

Вы можете легко использовать для этого нумерацию страниц.

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

необходимо передать new PageRequest(0, 1), чтобы получить записи, и из списка выберите первую запись.

person tk_    schedule 03.07.2019
comment
Согласно вопросу, я не хочу использовать setMaxResults, если возможно, вы дадите лучшее решение, просто поместив Pageable в качестве параметра в метод и вызывая его отправкой new PageRequest(0, 20). Я получаю первые 20 записей. Спасибо. - person David Jesus; 05.05.2021


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

Это сработало для меня без использования setmaxResults();

Просто укажите максимальное значение в последнем (в данном случае 5) без использования ключевого слова limit. :П

person Dilawar    schedule 12.01.2011
comment
Хм ... не слишком уверен в этом, [необходима цитата], это может быть несчастный случай и может внезапно перестать работать в новой версии спящего режима. - person Konrad 'ktoso' Malawski; 15.03.2011
comment
Пожалуйста, обратитесь к официальному документу. - person Do Nhu Vy; 22.01.2016
comment
Это не работает для меня в Hibernate версии 5.2.12 Final - person jDub9; 29.11.2018
comment
Это не работает, также не поддерживает спящий режим 4.x. - person Faiz Akram; 05.09.2019

По моим наблюдениям, даже если у вас есть ограничение в HQL (hibernate 3.x), это либо вызовет ошибку синтаксического анализа, либо просто проигнорирует. (если у вас есть заказ + desc / asc перед лимитом, он будет проигнорирован, если у вас нет desc / asc перед лимитом, это вызовет ошибку синтаксического анализа)

person Xingsheng    schedule 27.07.2010

Если можно управлять лимитом в этом режиме

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

Это действительно простой код для обработки лимита или списка.

person Community    schedule 28.10.2011

Вы можете использовать запрос ниже

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

Нижеприведенный фрагмент используется для выполнения запроса лимита с использованием HQL.

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

Вы можете получить демонстрационное приложение по адресу ссылка.

person Hari Krishna    schedule 17.03.2020

person    schedule
comment
Мне больше всего нравится этот, потому что setFirstResult фактически упоминается в этом ответе, тогда как здесь и в других местах они просто говорят setMaxResults это и setMaxResults, не говоря уже о том, как установить смещение. - person demongolem; 02.08.2014