Làm thế nào để bạn thực hiện một truy vấn giới hạn trong JPQL hoặc HQL?


239

Trong Hibernate 3, có cách nào để thực hiện tương đương với giới hạn MySQL sau trong HQL không?

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

Tôi không muốn sử dụng setMaxResults nếu có thể. Điều này chắc chắn là có thể trong phiên bản cũ của Hibernate / HQL, nhưng dường như nó đã biến mất.


2
Tôi đang sử dụng Hibernate-5.0.12. Điều này vẫn không có sẵn? Sẽ rất nặng nề để có được một triệu bản ghi hoặc sau đó áp dụng bộ lọc trên đó - setMaxResultsthông qua @Rachel trong câu trả lời của @skaffman.
Rajeev Ranjan

Câu trả lời:


313

Điều này đã được đăng trên diễn đàn Hibernate vài năm trước khi được hỏi về lý do tại sao điều này hoạt động trong Hibernate 2 nhưng không phải trong Hibernate 3:

Giới hạn không bao giờ là một điều khoản được hỗ trợ trong HQL. Bạn có nghĩa là sử dụng setMaxResults ().

Vì vậy, nếu nó hoạt động trong Hibernate 2, có vẻ như đó là do sự trùng hợp, thay vì thiết kế. Tôi nghĩ điều này là do trình phân tích cú pháp HQL Hibernate 2 sẽ thay thế các bit của truy vấn mà nó nhận ra là HQL và để phần còn lại như cũ, vì vậy bạn có thể lẻn vào một số SQL nguyên gốc. Tuy nhiên, Hibernate 3 có Trình phân tích cú pháp AST HQL thích hợp và nó ít tha thứ hơn.

Tôi nghĩ rằng Query.setMaxResults()thực sự là lựa chọn duy nhất của bạn.


3
Tôi cho rằng cách tiếp cận của Hibernate 3 là đúng hơn. Việc sử dụng Hibernate của bạn có nghĩa là bất khả tri về cơ sở dữ liệu, vì vậy bạn nên thực hiện các loại việc này một cách trừu tượng.
matt b

6
Tôi đồng ý, nhưng nó làm cho di cư là nỗi đau của hoàng gia khi các tính năng bị loại bỏ như thế.
skaffman

52
nhưng với setMaxResults, truy vấn đầu tiên được chạy và sau đó trên tập setMaxResultskết quả mà bạn gọi sẽ lấy số lượng hàng kết quả giới hạn từ resultset và hiển thị nó cho người dùng, trong trường hợp của tôi, tôi có 3 triệu bản ghi được truy vấn và sau đó gọi setMaxResults để đặt 50 bản ghi nhưng tôi không muốn làm điều đó, trong khi bản thân truy vấn tôi muốn truy vấn 50 bản ghi, có cách nào để làm điều đó không?
Rachel

2
Bài cũ tôi biết. Tôi hoàn toàn đồng ý với Rachel. Sử dụng NHibernate (cổng .Net của Hibernate), gần đây tôi đã nâng cấp từ 2 lên 3 và điều tương tự, top X hiện đang gây ra lỗi trình phân tích cú pháp. Tuy nhiên, khi tôi thêm setMaxResults vào truy vấn, nó đã tạo ra TOP X trong SQL kết quả (sử dụng MsSql2008Dialect). Điều này là tốt
Thierry_S

17
@Rachel Với setMaxResultshibernate sẽ nối thêm limitphần vào truy vấn. Nó sẽ không nhận được tất cả các kết quả. Bạn có thể kiểm tra truy vấn mà nó tạo ra bằng cách bật:<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
Tôi thích điều này nhất vì setFirstResultthực sự được đề cập trong câu trả lời này trong khi ở đây và ở nơi khác họ chỉ nói setMaxResultsđiều này và setMaxResultsđiều đó mà không đề cập đến cách đặt bù.
demongolem

19

Nếu bạn không muốn sử dụng setMaxResults()trên Queryđối tượng thì bạn luôn có thể quay lại sử dụng SQL bình thường.


37
Đó không thực sự là tất cả thú vị.
stevedbrown

8
Tôi cũng không thấy HQL thú vị. Tại sao không viết chế độ xem trên máy chủ DB của bạn áp dụng giới hạn và sau đó nhận HQL để xem chế độ xem đó: P
pjp

1
Đó chỉ là một trong những điều đó, trong khi SQL dễ dàng hơn HQL cho mỗi truy vấn, việc tạo các khung nhìn và viết SQL gốc có xu hướng không quá tuyệt vời để tái cấu trúc. Tôi cố gắng tránh nó khi tôi có thể. Vấn đề thực sự thực tế đó là tôi đã viết truy vấn MySQL của mình dù sao cũng sai và nghĩ setMaxResults là lạ. Không phải vậy.
stevedbrown

và nếu bạn cố gắng chuyển đổi giữa các nhà cung cấp DBMS khác nhau, nỗi đau đang chờ bạn.
Olgun Kaya

5

Nếu bạn không muốn sử dụng setMaxResults, bạn cũng có thể sử dụng Query.scroll thay vì danh sách và tìm nạp các hàng bạn muốn. Hữu ích cho phân trang chẳng hạn.


Cảm ơn, câu trả lời được chấp nhận không giải quyết được vấn đề cho tôi, vì setMaxResults()trước tiên hãy tải mọi bản ghi trong bộ nhớ và sau đó tạo một danh sách con, khi có hàng trăm nghìn bản ghi trở lên bị sập máy chủ, vì nó hết bộ nhớ. Tuy nhiên tôi có thể chuyển từ truy vấn gõ JPA sang truy vấn Hibernate thông qua QueryImpl hibernateQuery = query.unwrap(QueryImpl.class)và sau đó tôi có thể sử dụng scroll()phương thức như bạn đề xuất.
toridorges

Ít nhất với phương ngữ Oracle thì điều này không đúng (Hibernate sử dụng cột ảo ROWNUM). Có lẽ nó phụ thuộc vào người lái xe. Các DB khác có chức năng TOP.
Lluis Martinez

Truy vấn của tôi đang sử dụng một tìm nạp tham gia. Điều này dẫn đến cảnh báo Hibernate "firstResult / maxResults được chỉ định với bộ sưu tập tìm nạp; áp dụng trong bộ nhớ". Vì vậy, với một lần tìm nạp, Hibernate đang tải bộ sưu tập đầy đủ trong bộ nhớ. Bỏ tham gia là không có tùy chọn vì lý do hiệu suất. Khi tôi sử dụng ScrollableResults, tôi có nhiều quyền kiểm soát hơn về bản ghi nào được tải trong bộ nhớ. Tôi không thể tải tất cả các bản ghi với một ScrollableResults duy nhất, vì điều này cũng dẫn đến việc hết bộ nhớ. Tôi đang thử nghiệm tải nhiều trang với ScrollableResults khác nhau. Nếu điều này không hoạt động, tôi sẽ đi với SQL.
toridorges

Thật kỳ lạ, tôi chưa bao giờ gặp phải điều đó. Có đôi khi sử dụng JDBC thẳng là cách tốt nhất, đặc biệt cho các quy trình lớn / lô.
Lluis Martinez

Các mối quan hệ @OneToMany đang gây ra vấn đề của tôi. Nếu bằng cách nào đó tôi có thể thực thi hàm tổng hợp Oracle LISTAGG trong Hibernate để ghép nhiều giá trị thành một giá trị, thì tôi có thể bỏ các phép nối và thay thế chúng bằng một truy vấn con.
toridorges

2

Bạn có thể dễ dàng sử dụng phân trang cho việc này.

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

bạn phải vượt qua new PageRequest(0, 1)để tìm nạp các bản ghi và từ danh sách tìm nạp bản ghi đầu tiên.


2

Vì đây là một câu hỏi rất phổ biến, tôi đã viết bài viết này , trên đó câu trả lời này dựa trên.

Các setFirstResultsetMaxResults Queryphương pháp

Đối với JPA và Hibernate Query, setFirstResultphương thức này tương đương OFFSETsetMaxResultsphương thức này tương đương với GIỚI HẠN:

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

Sự LimitHandlertrừu tượng

Hibernate LimitHandlerđịnh nghĩa logic phân trang cụ thể cho cơ sở dữ liệu và như được minh họa bằng sơ đồ sau, Hibernate hỗ trợ nhiều tùy chọn phân trang cụ thể cho cơ sở dữ liệu:

Việc triển khai <code> LimitHandler </ code>

Bây giờ, tùy thuộc vào hệ thống cơ sở dữ liệu quan hệ cơ bản mà bạn đang sử dụng, truy vấn JPQL ở trên sẽ sử dụng cú pháp phân trang thích hợp.

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 ?

Máy chủ 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_ > ?

Ưu điểm của việc sử dụng setFirstResultsetMaxResultslà Hibernate có thể tạo cú pháp phân trang cụ thể cho cơ sở dữ liệu cho bất kỳ cơ sở dữ liệu quan hệ được hỗ trợ nào.

Và, bạn không bị giới hạn chỉ với các truy vấn JPQL. Bạn có thể sử dụng setFirstResultsetMaxResultsphương thức bảy cho các truy vấn SQL gốc.

Các truy vấn SQL gốc

Bạn không phải mã hóa phân trang cụ thể cho cơ sở dữ liệu khi sử dụng các truy vấn SQL gốc. Hibernate có thể thêm nó vào truy vấn của bạn.

Vì vậy, nếu bạn đang thực hiện truy vấn SQL này trên 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 sẽ biến đổi nó như sau:

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

Thật tuyệt phải không?

Ngoài phân trang dựa trên SQL

Phân trang là tốt khi bạn có thể lập chỉ mục các tiêu chí lọc và sắp xếp. Nếu các yêu cầu phân trang của bạn ngụ ý lọc động, thì đó là cách tiếp cận tốt hơn nhiều để sử dụng giải pháp đảo ngược chỉ mục, như ElasticSearch.

Kiểm tra bài viết này để biết thêm chi tiết.


1

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

Điều này làm việc cho tôi mà không sử dụng setmaxResults();

Chỉ cần cung cấp giá trị tối đa trong lần cuối (trong trường hợp này là 5) mà không cần sử dụng từ khóa limit. : P


7
Hừm ... không chắc lắm về điều này, [cần dẫn nguồn] đây có thể là một tai nạn và đột nhiên có thể ngừng hoạt động trong một phiên bản mới của ngủ đông.
Konrad 'ktoso' Malawski

4
Vui lòng tham khảo tài liệu chính thức.
Đỗ Như Vy

1
Điều này không hiệu quả với tôi trong Chung kết Hibernate Phiên bản 5.2.12
jDub9

1
Nó không hoạt động, cũng không hỗ trợ ngủ đông 4.x.
Faiz Akram

0

Quan sát của tôi là ngay cả khi bạn có giới hạn trong HQL (hibernate 3.x), nó sẽ gây ra lỗi phân tích cú pháp hoặc chỉ bị bỏ qua. (nếu bạn có thứ tự theo + desc / asc trước giới hạn, nó sẽ bị bỏ qua, nếu bạn không có desc / asc trước giới hạn, nó sẽ gây ra lỗi phân tích cú pháp)


0

Nếu có thể quản lý một giới hạn trong chế độ này

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

Đây là một mã thực sự đơn giản để xử lý một giới hạn hoặc một danh sách.


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

Bạn cần phải viết một truy vấn riêng, tham khảo điều này .

@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

Bạn có thể sử dụng truy vấn dưới đây

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

0

Đoạn dưới đây được sử dụng để thực hiện truy vấn giới hạn bằng HQL.

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

Bạn có thể nhận được ứng dụng demo tại liên kết này .


-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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.