Jak wykonać zapytanie o limit w JPQL lub HQL?

239

Czy w Hibernacji 3 można zrobić równowartość następującego limitu MySQL w HQL?

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

Nie chcę używać setMaxResults, jeśli to możliwe. Było to zdecydowanie możliwe w starszej wersji Hibernacji / HQL, ale wydaje się, że zniknęło.

stevedbrown
źródło
2
Używam Hibernate-5.0.12. Czy to wciąż nie jest dostępne? Byłoby naprawdę ciężko zdobyć około miliona rekordów, a następnie zastosować na nim filtr - setMaxResultsjak zauważył @Rachel w odpowiedzi @skaffman.
Rajeev Ranjan

Odpowiedzi:

313

Zostało to opublikowane na forum Hibernate kilka lat temu, gdy zapytano go, dlaczego to działa w Hibernacji 2, ale nie w Hibernacji 3:

Limit nigdy nie był obsługiwaną klauzulą ​​w HQL. Masz zamiar użyć setMaxResults ().

Więc jeśli zadziałało w Hibernacji 2, wygląda na to, że był to przypadek, a nie projekt. Myślę, że było tak, ponieważ parser Hibernacji 2 HQL zastąpiłby bity zapytania, które rozpoznał jako HQL, i zostawił resztę bez zmian, abyś mógł się zakraść w natywnym SQL. Hibernacja 3 ma jednak odpowiedni parser AST HQL i jest o wiele mniej wybaczający.

Myślę, Query.setMaxResults()że to naprawdę twoja jedyna opcja.

skaffman
źródło
3
Twierdziłbym, że podejście Hibernate 3 jest bardziej poprawne. Twoje użycie Hibernacji ma być niezależne od bazy danych, więc powinieneś robić tego rodzaju rzeczy w abstrakcyjny sposób.
matt b
6
Zgadzam się, ale powoduje, że migracja jest królewskim bólem w dupie, gdy takie cechy są upuszczane.
skaffman
52
ale w przypadku setMaxResults uruchamiane jest pierwsze zapytanie, setMaxResultsa następnie wywoływany zestaw wyników, który pobierałby ograniczoną liczbę wierszy wyników od zestawu wyników i wyświetlał go użytkownikowi, w moim przypadku mam 3 miliony rekordów, które są wyszukiwane, a następnie wywołuję setMaxResults, aby ustawić 50 rekordów, ale nie chcę tego robić, podczas gdy samo zapytanie chcę zapytać o 50 rekordów, czy jest na to sposób?
Rachel
2
Stary post, który znam. W pełni zgadzam się z Rachel. Korzystając z NHibernate (port .Net Hibernacji), ostatnio uaktualniłem z 2 do 3 i to samo, top X wyświetla teraz błąd parsera. Jednak gdy dodałem setMaxResults do zapytania, wygenerowało ono TOP X w wynikowym SQL (używając MsSql2008Dialect). To jest dobre.
Thierry_S,
17
@Rachel Z setMaxResultshibernacją doda limitczęść do zapytania. Nie uzyska wszystkich wyników. Możesz sprawdzić zapytanie, które generuje, włączając:<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);
Jessu
źródło
6
Najbardziej podoba mi się ten, ponieważ setFirstResultjest on rzeczywiście wymieniony w tej odpowiedzi, podczas gdy tutaj i gdzie indziej mówią tylko setMaxResultsto i setMaxResultsto bez wspominania, jak ustawić przesunięcie.
demongolem
19

Jeśli nie chcesz używać setMaxResults()tego Queryobiektu, zawsze możesz wrócić do używania normalnego SQL.

pjp
źródło
37
To nie jest tak ekscytujące.
stevedbrown
8
HQL nie jest też ekscytujący. Dlaczego nie napisać widoku na serwerze DB, który stosuje limit, a następnie poprosić HQL, aby spojrzał na ten widok: P
pjp
1
To tylko jedna z tych rzeczy, podczas gdy SQL jest o wiele łatwiejszy niż HQL dla każdego zapytania, tworzenie widoków i pisanie natywnego SQL nie jest tak świetne do refaktoryzacji. Staram się tego unikać, kiedy mogę. Rzeczywistym prawdziwym problemem było to, że i tak napisałem moje zapytanie MySQL i myślałem, że setMaxResults jest dziwny. Nie było
stevedbrown
a jeśli spróbujesz przełączać się między różnymi dostawcami DBMS, czeka na ciebie ból.
Olgun Kaya,
5

Jeśli nie chcesz używać setMaxResults, możesz także użyć Query.scroll zamiast listy i pobrać żądane wiersze. Przydatne na przykład dla stronicowania.

Lluis Martinez
źródło
Dzięki, zaakceptowana odpowiedź nie rozwiązała dla mnie problemu, ponieważ setMaxResults()najpierw ładuje każdy rekord w pamięci, a następnie tworzy listę podrzędną, która, gdy jest sto tysięcy lub więcej rekordów, powoduje awarię serwera, ponieważ brakuje pamięci. Mogłem jednak przejść z zapytania o typie JPA do zapytania Hibernacja, QueryImpl hibernateQuery = query.unwrap(QueryImpl.class)a następnie mogłem użyć scroll()metody zgodnie z sugestią.
toongeorges
Przynajmniej w przypadku dialektu Oracle nie jest to prawdą (Hibernacja korzysta z wirtualnej kolumny ROWNUM). Może to zależy od kierowcy. Inne DB mają funkcję TOP.
Lluis Martinez
Moje zapytanie używa pobierania dołączenia. Powoduje to ostrzeżenie Hibernacja „firstResult / maxResults określone przy pobieraniu kolekcji; zastosowanie w pamięci”. Tak więc przy pobieraniu złączeń Hibernacja ładuje całą kolekcję do pamięci. Porzucenie złączenia nie jest możliwe ze względu na wydajność. Kiedy używam ScrollableResults, mam większą kontrolę nad tym, które rekordy są ładowane do pamięci. Nie mogę załadować wszystkich rekordów za pomocą jednego ScrollableResults, ponieważ powoduje to również brak pamięci. Próbuję ładować wiele stron z różnymi ScrollableResults. Jeśli to nie zadziała, przejdę do SQL.
toongeorges
To dziwne, nigdy tego nie spotkałem. Tak, czasem wystarczy użyć prostego JDBC, szczególnie w przypadku procesów masowych / wsadowych.
Lluis Martinez
Relacje @OneToMany powodują moje problemy. Jeśli w jakiś sposób mógłbym wykonać funkcję agregującą Oracle LISTAGG w Hibernacji, aby połączyć wiele wartości w jedną wartość, to mogę porzucić połączenia i zastąpić je podzapytaniem.
toongeorges
2

Możesz w tym celu łatwo zastosować podział na strony.

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

musisz przejść, new PageRequest(0, 1)aby pobrać rekordy, a z listy pobierz pierwszy rekord.

tk_
źródło
2

Ponieważ jest to bardzo częste pytanie, napisałem ten artykuł , na którym opiera się ta odpowiedź.

setFirstResultI setMaxResults Querymetody

Dla JPA i hibernacji QueryThe setFirstResultmetoda jest równoważne OFFSET, a setMaxResultssposób jest odpowiednikiem dopuszczalnych:

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

LimitHandlerabstrakcja

Hibernacja LimitHandlerdefiniuje logikę stronicowania specyficzną dla bazy danych i jak pokazano na poniższym diagramie, Hibernacja obsługuje wiele opcji stronicowania specyficznych dla bazy danych:

Implementacje <code> LimitHandler </code>

Teraz, w zależności od bazowego systemu relacyjnej bazy danych, którego używasz, powyższe zapytanie JPQL będzie używać właściwej składni stronicowania.

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

Wyrocznia

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

Zaletą używania setFirstResulti setMaxResultsjest to, że Hibernacja może generować specyficzną dla bazy danych składnię stronicowania dla dowolnych obsługiwanych relacyjnych baz danych.

I nie jesteś ograniczony tylko do zapytań JPQL. Możesz użyć metody setFirstResulti setMaxResultssiódmej do natywnych zapytań SQL.

Natywne zapytania SQL

Podczas korzystania z rodzimych zapytań SQL nie trzeba kodować stronicowania specyficznego dla bazy danych. Hibernacja może dodać to do twoich zapytań.

Jeśli więc wykonujesz to zapytanie SQL na 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();

Hibernacja przekształci go w następujący sposób:

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

Fajnie, prawda?

Podział na strony poza SQL

Podział na strony jest dobry, gdy można indeksować kryteria filtrowania i sortowania. Jeśli twoje wymagania stronicowania wymagają dynamicznego filtrowania, o wiele lepiej jest zastosować rozwiązanie z indeksem odwróconym, takie jak ElasticSearch.

Sprawdź ten artykuł, aby uzyskać więcej informacji.

Vlad Mihalcea
źródło
1

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

To działało dla mnie bez użycia setmaxResults();

Wystarczy podać maksymalną wartość w ostatnim (w tym przypadku 5) bez użycia słowa kluczowego limit. : P

Dilawar
źródło
7
Hm ... nie jestem tego pewien, [potrzebne źródło] może to być wypadek i może nagle przestać działać w nowej wersji hibernacji.
Konrad „ktoso” Malawski
4
Proszę odnieść się do oficjalnego dokumentu.
Do Nhu Vy
1
To nie działa dla mnie w Hibernacji Wersja 5.2.12 wersja ostateczna
jDub9
1
Nie działa, nie obsługuje także hibernacji 4.x.
Faiz Akram
0

Moje spostrzeżenie jest takie, że nawet ty masz limit w HQL (hibernacja 3.x), będzie to albo powodować błąd parsowania, albo po prostu zostanie zignorowany. (jeśli masz zamówienie przez + desc / asc przed limitem, zostanie zignorowane, jeśli nie masz desc / asc przed limitem, spowoduje błąd parsowania)

Xingsheng
źródło
0

Jeśli można zarządzać limitem w tym trybie

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

To jest naprawdę prosty kod do obsługi limitu lub listy.

biancardi
źródło
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());
            }
Deep Chand Jaipur
źródło
0

Musisz napisać natywne zapytanie, zapoznaj się z tym .

@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);
swatisinghi
źródło
0

Możesz użyć poniższego zapytania

NativeQuery<Object[]> query = session.createNativeQuery(select * from employee limit ?)
query.setparameter(1,1);
Virendra Khade
źródło
0

Poniższy fragment służy do wykonywania zapytań o limit przy użyciu HQL.

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

Możesz pobrać aplikację demonstracyjną pod tym linkiem .

Hari Krishna
źródło
-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);
Leena Varshney
źródło