Как вы делаете лимитный запрос в JPQL или HQL?


239

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

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

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


2
Я использую Hibernate-5.0.12. Это все еще не доступно? Было бы очень тяжело получить около миллиона записей, а затем применить к ним фильтр, setMaxResultsкак заметил @Rachel в ответе @skaffman.
Раджив Ранджан

Ответы:


313

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

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

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

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


3
Я бы сказал, что подход Hibernate 3 является более правильным. Использование Hibernate подразумевает независимость от базы данных, поэтому вы должны делать такие вещи абстрактно.
Мэтт б

6
Я согласен, но это делает миграцию королевской болью в заднице, когда функции отбрасываются таким образом.
Скаффман

52
но с setMaxResults, первый запрос выполняется, а затем на наборе результатов, который вы вызываете, setMaxResultsкоторый будет принимать ограниченное количество строк результатов из набора результатов и отображать его пользователю, в моем случае у меня есть 3 миллиона записей, которые запрашиваются, а затем вызываю setMaxResults для установки 50 записей, но я не хочу этого делать, хотя сам запрос я хочу запросить 50 записей, есть ли способ сделать это?
Рэйчел

2
Старый пост я знаю. Я полностью согласен с Рейчел. Используя NHibernate (.Net порт Hibernate), я недавно обновил с 2 до 3 и то же самое, top X теперь выдает ошибку синтаксического анализатора. Однако когда я добавил setMaxResults в запрос, он сгенерировал TOP X в результирующем SQL (используя MsSql2008Dialect). Это хорошо.
Thierry_S

17
@Rachel С setMaxResultsHibernate добавит limitчасть к запросу. Он не получит все результаты. Вы можете проверить запрос, который он производит, включив:<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
Мне это нравится больше всего, потому что setFirstResultна самом деле упоминается в этом ответе, тогда как здесь и в других местах они просто говорят setMaxResultsэто и setMaxResultsто, не упоминая, как установить смещение.
demongolem

19

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


37
Это не так уж и интересно.
Стивед Браун

8
Я не нахожу HQL захватывающим. Почему бы не написать представление на вашем сервере БД, которое применяет ограничение, а затем заставить HQL взглянуть на это представление: P
pjp

1
Это просто одна из тех вещей, в то время как SQL намного проще, чем HQL для каждого запроса, создание представлений и написание нативного SQL, как правило, не очень хорошо для рефакторинга. Я стараюсь избегать этого, когда могу. Эта реальная проблема заключалась в том, что я все равно неправильно написал свой запрос MySQL и подумал, что setMaxResults странно. Не было
Стивед Браун

и если вы попытаетесь переключиться между разными поставщиками СУБД, вас ждет боль.
Олгун Кая

5

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


Спасибо, принятый ответ не решил проблему для меня, потому что setMaxResults()сначала загружает каждую запись в памяти, а затем создает подсписок, который при сотнях тысяч или более записей приводит к сбою сервера, потому что ему не хватает памяти. Однако я мог бы перейти от запроса с типом JPA к запросу Hibernate, QueryImpl hibernateQuery = query.unwrap(QueryImpl.class)а затем я мог бы использовать scroll()метод, как вы предложили.
Toongorges

По крайней мере, с диалектом Oracle это не так (Hibernate использует виртуальный столбец ROWNUM). Может быть, это зависит от водителя. Другие БД имеют функцию TOP.
Луис Мартинес

Мой запрос использует выборку соединения. Это приводит к появлению предупреждения Hibernate «firstResult / maxResults, указанное в выборке коллекции; применяется в памяти». Таким образом, используя выборку соединений, Hibernate загружает всю коллекцию в память. Отбрасывание объединения не является вариантом по соображениям производительности. Когда я использую ScrollableResults, у меня больше контроля над тем, какие записи загружаются в память. Я не могу загрузить все записи с одним ScrollableResults, потому что это также приводит к нехватке памяти. Я экспериментирую с загрузкой нескольких страниц с разными ScrollableResults. Если это не работает, я пойду с SQL.
Toongorges

Это странно, я никогда не сталкивался с этим. Да, иногда нужно использовать прямой JDBC, особенно для массовых / пакетных процессов.
Луис Мартинес

Отношения @OneToMany вызывают мои проблемы. Если я каким-то образом смогу выполнить агрегатную функцию Oracle LISTAGG в Hibernate, чтобы объединить несколько значений в одно, то я могу отбросить объединения и заменить их подзапросом.
Toongorges

2

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

    @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)чтобы получить записи и из списка получить первую запись.


2

Поскольку это очень распространенный вопрос, я написал эту статью , на которой основан этот ответ.

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 поддерживает множество опций разбивки на базы данных:

<code> LimitHandler </ code> реализации

Теперь, в зависимости от используемой вами системы реляционной базы данных, вышеприведенный запрос 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

оракул

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.

Проверьте эту статью для более подробной информации.


1

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

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

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


7
Хм ... не слишком уверен в этом, [цитата нужна], это может быть несчастным случаем и может внезапно перестать работать в новой версии hibernate.
Конрад 'Ктосо' Малавский

4
Пожалуйста, обратитесь к официальному документу.
До Нху Вы

1
Это не работает для меня в Hibernate версии 5.2.12 Final
jDub9

1
Это не работает, не поддерживает Hibernate 4.x также.
Файз Акрам

0

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


0

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

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

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


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

Вам нужно написать собственный запрос, обратитесь к этому .

@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

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

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

0

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

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

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


-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);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.