268

In Hibernate 3, is there a way to do the equivalent of the following MySQL limit in HQL?

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

I don't want to use setMaxResults if possible. This definitely was possible in the older version of Hibernate/HQL, but it seems to have disappeared.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
stevedbrown
  • 8,862
  • 8
  • 43
  • 58
  • 2
    I am using `Hibernate-5.0.12`. Is this still not available? It would be really heavy to get a million or so records and then apply the filter on it- `setMaxResults` over it as noticed by @Rachel in the answer by @skaffman. – Rajeev Ranjan Oct 23 '17 at 08:59

14 Answers14

336

This was posted on the Hibernate forum a few years back when asked about why this worked in Hibernate 2 but not in Hibernate 3:

Limit was never a supported clause in HQL. You are meant to use setMaxResults().

So if it worked in Hibernate 2, it seems that was by coincidence, rather than by design. I think this was because the Hibernate 2 HQL parser would replace the bits of the query that it recognised as HQL, and leave the rest as it was, so you could sneak in some native SQL. Hibernate 3, however, has a proper AST HQL Parser, and it's a lot less forgiving.

I think Query.setMaxResults() really is your only option.

skaffman
  • 398,947
  • 96
  • 818
  • 769
  • 1
    I was hoping somehow that they realized that that is annoying. Also, if you looked at the source code, they had implemented it for every database, and every database is different. Oh well, that's sad. – stevedbrown Aug 06 '09 at 15:45
  • 3
    I would argue that Hibernate 3's approach is more correct. Your usage of Hibernate is meant to be database-agnostic, so you should have to do these sorts of things in an abstract manner. – matt b Aug 06 '09 at 16:35
  • 6
    I agree, but it makes migration is royal pain in the ass when features are dropped like that. – skaffman Aug 06 '09 at 16:38
  • 58
    but with setMaxResults, first query is run and then on the resultset you call `setMaxResults` which would take limited number of result rows from resultset and display it to the user, in my case i have 3 million records which are queried and then am calling setMaxResults to set 50 records but i do not want to do that, while query itself i want to query for 50 records, is there a way to do that? – Rachel Jan 27 '12 at 20:06
  • 3
    Old post I know. I fully agree with Rachel. Using NHibernate (.Net port of Hibernate), I've recently upgraded from 2 to 3 and same thing, top X is now throwing a parser error. However, when I added setMaxResults on the query, it did generate a TOP X in the resulting SQL (using MsSql2008Dialect). This is good. – Thierry_S Nov 04 '13 at 17:00
  • 18
    @Rachel With `setMaxResults` hibernate will append the `limit` part to the query. It will not get all the results. You can check the query it produces by enabling: `true` – Andrejs Dec 23 '14 at 09:55
  • It would only make sense that this feature is not supported by all databases. For example: Oracle's SQL does not have a `limit` or `top` keyword, it has a `rownum` though, but you can't use it in combination with sorting, unless you wrap it (i.e. use subqueries). But that makes it hard to combine it with a `join`. MySQL and Teradata use `top` and requires it to be right in front of the query, but mysql requires it to be all the way at the back using the `limit` keyword. I have no trouble believing that MySQL and SQL Server support it, but I would be surprised if Oracle did. – bvdb Jul 23 '15 at 22:42
  • I don't think it didn't work in hibernate 3. In jboss 5.1.0.GA which included Hibernate 3.3.1 limit was working fine. – Marinos An Mar 07 '17 at 12:17
  • 1
    @Andrejs when the query uses fetch joins, it appears that `setMaxResults()` does not add `limit` to the query (for Oracle DB), but loads all the records in memory and does the reduction of the number of records there. When you have millions of matching records, that means an OutOfMemoryError (or related error) – toongeorges Jun 20 '17 at 16:55
  • I am using `Hibernate-5.0.12`. Is this still not available? It would be really heavy to get a million or so records and then apply the filter it- `setMaxResults` over it as noticed by @Rachel. – Rajeev Ranjan Oct 23 '17 at 07:12
  • Take a look at this page which explains the rationale behind this decision: https://dzone.com/articles/hibernate-tuning-queries-using In particular, the LIMIT keyword is not database-agnostic, so setMaxResults is needed to convert the query to the appropriate keyword in your dialect. Second, if the query involves a FETCH JOIN, the LIMIT keyword will not make sense since it will apply to rows, not parent entities. Hence Hibernate does not include it. Getting a small piece of data from many large tables is still a problem, but hopefully this will shed light on Hibernate's limitations here. – Mattias Martens Nov 30 '17 at 21:46
158
 // SQL: SELECT * FROM table LIMIT start, maxRows;

Query q = session.createQuery("FROM table");
q.setFirstResult(start);
q.setMaxResults(maxRows);
Jessu
  • 2,069
  • 1
  • 15
  • 19
  • 6
    I like this one best because `setFirstResult` is actually mentioned in this answer whereas here and elsewhere they just say `setMaxResults` this and `setMaxResults` that without mentioning how to set the offset. – demongolem Aug 01 '14 at 20:27
21

If you don't want to use setMaxResults() on the Query object then you could always revert back to using normal SQL.

pjp
  • 17,039
  • 6
  • 33
  • 58
  • 47
    That's not really all that exciting. – stevedbrown Aug 06 '09 at 15:39
  • 9
    I don't find HQL exciting either. Why not write a view on your DB server that applies the limit and then get HQL to look at that view :P – pjp Aug 06 '09 at 15:59
  • 1
    It's just one of those things, while SQL is much easier than HQL for each query, creating views and writing native SQL tends to be not so great for refactoring. I try to avoid it when I can. That actual real problem was the I wrote my MySQL query wrong anyways and thought setMaxResults was being weird. It wasn't. – stevedbrown Aug 06 '09 at 16:07
  • 1
    and if you try to switch between different DBMS vendors, pain is waiting for you. – Olgun Kaya Nov 06 '18 at 10:57
  • instead of using limit I used pageable to avoid the extra data – mohamed amine salah Jan 25 '21 at 17:05
13

The setFirstResult and setMaxResults Query methods

For a JPA and Hibernate Query, the setFirstResult method is the equivalent of OFFSET, and the setMaxResults method is the equivalent of LIMIT:

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

The LimitHandler abstraction

The Hibernate LimitHandler defines the database-specific pagination logic, and as illustrated by the following diagram, Hibernate supports many database-specific pagination options:

LimitHandler implementations

Now, depending on the underlying relational database system you are using, the above JPQL query will use the proper pagination syntax.

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

The advantage of using setFirstResult and setMaxResults is that Hibernate can generate the database-specific pagination syntax for any supported relational databases.

And, you are not limited to JPQL queries only. You can use the setFirstResult and setMaxResults method seven for native SQL queries.

Native SQL queries

You don't have to hardcode the database-specific pagination when using native SQL queries. Hibernate can add that to your queries.

So, if you're executing this SQL query on 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 will transform it as follows:

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

Cool, right?

Beyond SQL-based pagination

Pagination is good when you can index the filtering and sorting criteria. If your pagination requirements imply dynamic filtering, it's a much better approach to use an inverted-index solution, like ElasticSearch.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Using OpenJPA, I cannot make this work for native queries in DB2. setMaxResults and setFirstResult are ignored, only the native query I'm passing is sent – Edu Castrillon Jun 15 '22 at 13:31
6

You can easily use pagination for this.

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

you have to pass new PageRequest(0, 1)to fetch records and from the list fetch the first record.

tk_
  • 16,415
  • 8
  • 80
  • 90
  • 1
    According to the question "I don't want to use setMaxResults if possible" you give the better solution, just putting `Pageable` as a parameter in the method and calling it sending "`new PageRequest(0, 20)`" I'm getting the first 20 records. Thanks. – David Jesus May 05 '21 at 00:31
5

If you don't want to use setMaxResults, you can also use Query.scroll instead of list, and fetch the rows you desire. Useful for paging for instance.

Lluis Martinez
  • 1,963
  • 8
  • 28
  • 42
  • Thanks, the accepted answer did not solve the issue for me, because `setMaxResults()` loads first every record in memory and then creates a sublist, which when there are hundred thousands or more records crashes the server, because it is out of memory. I could however go from a JPA typed query to a Hibernate query through `QueryImpl hibernateQuery = query.unwrap(QueryImpl.class)` and then I could use the `scroll()` method as you suggested. – toongeorges Jun 21 '17 at 14:59
  • At least with Oracle dialect this is not true (Hibernate uses the ROWNUM virtual column). Maybe it depends on the driver. Other DBs have the TOP function. – Lluis Martinez Jun 22 '17 at 11:55
  • My query is using a join fetch. This results in the Hibernate warning "firstResult/maxResults specified with collection fetch; applying in memory". So with a join fetch, Hibernate is loading the full collection in memory. Dropping the join is no option because of performance reasons. When I use ScrollableResults, I have more control about which records are loaded in memory. I cannot load all records with a single ScrollableResults, because this also results in out of memory. I am experimenting loading multiple pages with different ScrollableResults. If this does not work, I will go with SQL. – toongeorges Jun 22 '17 at 12:52
  • That's weird, I've never encountered that. Yes sometimes using straight JDBC is the way to go, specially for massive/batch processes. – Lluis Martinez Jun 22 '17 at 14:35
  • The @OneToMany relations are causing my issues. If somehow I could execute the Oracle aggregate function LISTAGG in Hibernate to concatenate the multiple values to a single value, then I can drop the joins and replace them by a subquery. – toongeorges Jun 22 '17 at 15:13
  • I added some new fields to my entity that I annotated with @Formula. With the formula I call the Oracle LISTAGG function. Therefore, I do not need to join fetch the @OneToMany relations anymore. Without the join fetches, I can page my JPA query with `setMaxResults()`. – toongeorges Jun 22 '17 at 17:38
  • If your app is Oracle only then fine. – Lluis Martinez Jun 23 '17 at 09:17
2

You need to write a native query, refer this.

@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
  • 667
  • 7
  • 9
1

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

This worked for me without using setmaxResults();

Just provide the max value in the last (in this case 5) without using the keyword limit. :P

Casey
  • 12,070
  • 18
  • 71
  • 107
Dilawar
  • 51
  • 1
  • 1
0

My observation is that even you have limit in the HQL (hibernate 3.x), it will be either causing parsing error or just ignored. (if you have order by + desc/asc before limit, it will be ignored, if you don't have desc/asc before limit, it will cause parsing error)

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

You can use below query

NativeQuery<Object[]> query = session.createNativeQuery("select * from employee limit ?");
query.setparameter(1,1);
General Grievance
  • 4,555
  • 31
  • 31
  • 45
0

Below snippet is used to perform limit query using HQL.

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

You can get demo application at this link.

Hari Krishna
  • 3,658
  • 1
  • 36
  • 57
0

If can manage a limit in this 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();
}

This is a really simple code to handle a limit or a list.

alexandrum
  • 429
  • 9
  • 17
-4
@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);
Andreas
  • 2,455
  • 10
  • 21
  • 24