JPA: mẫu thích hợp để lặp qua các tập kết quả lớn là gì?


114

Giả sử tôi có một bảng với hàng triệu hàng. Sử dụng JPA, cách thích hợp để lặp lại một truy vấn đối với bảng đó là gì, sao cho tôi không có tất cả Danh sách trong bộ nhớ với hàng triệu đối tượng?

Ví dụ, tôi nghi ngờ rằng những thứ sau sẽ bị nổ nếu bàn lớn:

List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();

for (Model model : models)
{
     System.out.println(model.getId());
}

Phân trang (lặp và cập nhật thủ công setFirstResult()/ setMaxResult()) có thực sự là giải pháp tốt nhất không?

Chỉnh sửa : trường hợp sử dụng chính mà tôi đang nhắm mục tiêu là một loại công việc hàng loạt. Nếu chạy lâu cũng được. Không có ứng dụng khách web nào tham gia; Tôi chỉ cần "làm gì đó" cho từng hàng, một (hoặc một số N nhỏ) tại một thời điểm. Tôi chỉ cố gắng tránh để tất cả chúng trong bộ nhớ cùng một lúc.


Bạn đang sử dụng cơ sở dữ liệu và trình điều khiển JDBC nào?

Câu trả lời:


55

Trang 537 của Java Persistence with Hibernate đưa ra giải pháp sử dụng ScrollableResults, nhưng than ôi nó chỉ dành cho Hibernate.

Vì vậy, có vẻ như việc sử dụng setFirstResult/ setMaxResultsvà lặp lại thủ công thực sự là cần thiết. Đây là giải pháp của tôi sử dụng JPA:

private List<Model> getAllModelsIterable(int offset, int max)
{
    return entityManager.createQuery("from Model m", Model.class).setFirstResult(offset).setMaxResults(max).getResultList();
}

sau đó, sử dụng nó như thế này:

private void iterateAll()
{
    int offset = 0;

    List<Model> models;
    while ((models = Model.getAllModelsIterable(offset, 100)).size() > 0)
    {
        entityManager.getTransaction().begin();
        for (Model model : models)
        {
            log.info("do something with model: " + model.getId());
        }

        entityManager.flush();
        entityManager.clear();
        em.getTransaction().commit();
        offset += models.size();
    }
}

33
Tôi nghĩ rằng ví dụ này không an toàn nếu có chèn mới trong quá trình hàng loạt. Người dùng phải sắp xếp thứ tự dựa trên một cột mà ở đó chắc chắn rằng dữ liệu mới được chèn vào sẽ nằm ở cuối danh sách kết quả.
Balazs Zsoldos

khi trang hiện tại là trang cuối cùng và có ít hơn 100 phần tử được kiểm tra size() == 100thay thế sẽ bỏ qua một truy vấn bổ sung trả về danh sách trống
cdalxndr

38

Tôi đã thử các câu trả lời được trình bày ở đây, nhưng JBoss 5.1 + MySQL Connector / J 5.1.15 + Hibernate 3.3.2 không hoạt động với những câu trả lời đó. Chúng tôi vừa mới chuyển từ JBoss 4.x sang JBoss 5.1, vì vậy chúng tôi đã mắc kẹt với nó ngay bây giờ và do đó, Hibernate mới nhất mà chúng tôi có thể sử dụng là 3.3.2.

Thêm một vài tham số bổ sung đã thực hiện công việc và mã như thế này chạy mà không có OOME:

        StatelessSession session = ((Session) entityManager.getDelegate()).getSessionFactory().openStatelessSession();

        Query query = session
                .createQuery("SELECT a FROM Address a WHERE .... ORDER BY a.id");
        query.setFetchSize(Integer.valueOf(1000));
        query.setReadOnly(true);
        query.setLockMode("a", LockMode.NONE);
        ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
        while (results.next()) {
            Address addr = (Address) results.get(0);
            // Do stuff
        }
        results.close();
        session.close();

Các dòng quan trọng là các tham số truy vấn giữa createQuery và scroll. Nếu không có chúng, lệnh gọi "cuộn" sẽ cố gắng tải mọi thứ vào bộ nhớ và không bao giờ kết thúc hoặc chạy đến OutOfMemoryError.


2
Xin chào Zds, trường hợp sử dụng của bạn là quét hàng triệu hàng chắc chắn là phổ biến đối với tôi và CẢM ƠN BẠN đã đăng mã cuối cùng. Trong trường hợp của tôi, tôi đang chuyển các bản ghi vào Solr, để lập chỉ mục chúng cho tìm kiếm toàn văn bản. Và, do các quy tắc kinh doanh, tôi sẽ không đi sâu vào, tôi cần phải đi qua Hibernate, thay vì chỉ sử dụng các mô-đun tích hợp sẵn của JDBC hoặc Solr.
Mark Bennett

Vui vẻ giúp đỡ :-). Chúng tôi cũng đang xử lý các tập dữ liệu lớn, trong trường hợp này cho phép người dùng truy vấn tất cả các tên đường trong cùng một thành phố / quận hoặc đôi khi thậm chí là tiểu bang, vì vậy việc tạo chỉ số yêu cầu đọc nhiều dữ liệu.
Zds

Xuất hiện với MySQL, bạn thực sự phải trải qua tất cả các vòng đó: stackoverflow.com/a/20900045/32453 (các DB khác có thể ít nghiêm ngặt hơn mà tôi tưởng tượng ...)
rogerdpack

32

Bạn thực sự không thể làm điều này trong JPA thẳng, tuy nhiên Hibernate có hỗ trợ cho các phiên không trạng thái và các tập kết quả có thể cuộn.

Chúng tôi thường xuyên xử lý hàng tỷ hàng với sự trợ giúp của nó.

Đây là liên kết đến tài liệu: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession


17
Cảm ơn. Thật tốt khi biết ai đó đang thực hiện hàng tỷ hàng thông qua Hibernate. Một số người ở đây khẳng định điều đó là không thể. :-)
George Armhold

2
Có thể thêm một ví dụ ở đây không? Tôi cho rằng nó tương tự như ví dụ của Zds?
rogerdpack

19

Thành thật mà nói, tôi khuyên bạn nên rời JPA và gắn bó với JDBC (nhưng chắc chắn là sử dụng JdbcTemplatelớp hỗ trợ hoặc tương tự). JPA (và các nhà cung cấp / thông số kỹ thuật ORM khác) không được thiết kế để hoạt động trên nhiều đối tượng trong một giao dịch vì họ cho rằng mọi thứ được tải phải ở trong bộ nhớ cache cấp một (do đó cần có clear()trong JPA).

Ngoài ra, tôi đề xuất giải pháp cấp thấp hơn vì chi phí của ORM (phản ánh chỉ là phần nổi của tảng băng) có thể rất quan trọng, đến mức lặp lại đơn giản ResultSet, thậm chí sử dụng một số hỗ trợ nhẹ như đã đề cậpJdbcTemplate sẽ nhanh hơn nhiều.

JPA đơn giản không được thiết kế để thực hiện các hoạt động trên một lượng lớn các thực thể. Bạn có thể chơi với flush()/ clear()để tránh OutOfMemoryError, nhưng hãy xem xét điều này một lần nữa. Bạn thu được rất ít khi phải trả giá bằng việc tiêu thụ tài nguyên khổng lồ.


Ưu điểm của JPA không chỉ là cơ sở dữ liệu bất khả tri mà còn là khả năng thậm chí không sử dụng cơ sở dữ liệu truyền thống (NoSQL). Không khó để thực hiện việc xả / xóa thỉnh thoảng và thường các hoạt động hàng loạt được thực hiện không thường xuyên.
Adam Gent

1
Chào Thomasz. Tôi có rất nhiều lý do để phàn nàn về JPA / Hibernate, nhưng tôi thực sự nghi ngờ rằng chúng "không được thiết kế để hoạt động trên nhiều đối tượng". Tôi nghi ngờ rằng tôi chỉ cần tìm hiểu mẫu thích hợp cho trường hợp sử dụng này.
George Armhold

4
Chà, tôi chỉ có thể nghĩ đến hai mẫu: phân trang (được đề cập nhiều lần) và flush()/ clear(). Cái đầu tiên là IMHO không được thiết kế cho mục đích xử lý hàng loạt, trong khi sử dụng chuỗi flush () / clear () có mùi giống như sự trừu tượng bị rò rỉ .
Tomasz Nurkiewicz

Đúng, nó là sự kết hợp giữa phân trang và flush / clear như bạn đã đề cập. Cảm ơn!
George Armhold

7

Nếu bạn sử dụng EclipseLink I 'sử dụng phương pháp này để nhận kết quả là có thể lặp lại

private static <T> Iterable<T> getResult(TypedQuery<T> query)
{
  //eclipseLink
  if(query instanceof JpaQuery) {
    JpaQuery<T> jQuery = (JpaQuery<T>) query;
    jQuery.setHint(QueryHints.RESULT_SET_TYPE, ResultSetType.ForwardOnly)
       .setHint(QueryHints.SCROLLABLE_CURSOR, true);

    final Cursor cursor = jQuery.getResultCursor();
    return new Iterable<T>()
    {     
      @SuppressWarnings("unchecked")
      @Override
      public Iterator<T> iterator()
      {
        return cursor;
      }
    }; 
   }
  return query.getResultList();  
}  

đóng phương pháp

static void closeCursor(Iterable<?> list)
{
  if (list.iterator() instanceof Cursor)
    {
      ((Cursor) list.iterator()).close();
    }
}

6
Đối tượng jQuery đẹp
usr-local-ΕΨΗΕΛΩΝ

Tôi đã thử mã của bạn nhưng vẫn nhận được OOM - có vẻ như tất cả các đối tượng T (và tất cả các đối tượng bảng được kết hợp được tham chiếu từ T) không bao giờ là GC. Hồ sơ cho thấy chúng được tham chiếu từ "bảng" trong org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork cùng với org.eclipse.persistence.internal.identitymaps.CacheKey. Tôi đã xem xét bộ nhớ cache và tất cả các cài đặt của tôi đều là mặc định (Vô hiệu hóa có chọn lọc, Yếu với bộ đệm con mềm, Kích thước bộ nhớ cache 100, Bỏ không hợp lệ). Tôi sẽ xem xét các phiên vô hiệu hóa và xem liệu nó có hữu ích không. BTW Tôi chỉ cần lặp lại con trỏ trả về bằng cách sử dụng "for (T o: results)".
Edi Bice,

Badum tssssssss
dctremblay

5

Nó phụ thuộc vào loại hoạt động bạn phải làm. Tại sao bạn lặp lại hơn một triệu hàng? Bạn có đang cập nhật thứ gì đó ở chế độ hàng loạt không? Bạn sẽ hiển thị tất cả các bản ghi cho khách hàng? Bạn có đang tính toán một số thống kê về các thực thể được truy xuất không?

Nếu bạn định hiển thị một triệu bản ghi cho khách hàng, vui lòng xem xét lại giao diện người dùng của bạn. Trong trường hợp này, giải pháp thích hợp là phân trang kết quả của bạn và sử dụng setFirstResult()setMaxResult().

Nếu bạn đã khởi chạy bản cập nhật cho một lượng lớn bản ghi, tốt hơn bạn nên giữ cho bản cập nhật đơn giản và dễ sử dụng Query.executeUpdate(). Theo tùy chọn, bạn có thể thực thi cập nhật ở chế độ không đồng bộ bằng cách sử dụng Trình quản lý công việc theo hướng thông báo Bean oa.

Nếu bạn đang tính toán một số thống kê trên các thực thể được truy xuất, bạn có thể tận dụng các chức năng nhóm được xác định bởi đặc tả JPA.

Đối với bất kỳ trường hợp nào khác, vui lòng nói rõ hơn :)


Rất đơn giản, tôi cần làm một cái gì đó "cho mỗi" hàng. Chắc chắn đây là một trường hợp sử dụng phổ biến. Trong trường hợp cụ thể mà tôi đang làm việc bây giờ, tôi cần truy vấn một dịch vụ web bên ngoài hoàn toàn nằm ngoài cơ sở dữ liệu của tôi, sử dụng một id (PK) từ mỗi hàng. Kết quả không được hiển thị trở lại bất kỳ trình duyệt web của ứng dụng khách nào, vì vậy không có giao diện người dùng nào để nói. Nói cách khác, đó là một công việc hàng loạt.
George Armhold

Nếu bạn "cần" id in cho mỗi hàng, không có cách nào khác là lấy từng hàng, lấy id và in. Giải pháp tốt nhất phụ thuộc vào những gì bạn cần làm.
Dainius

@Caffeine Coma, nếu bạn chỉ cần id của mỗi hàng thì cải tiến lớn nhất có lẽ sẽ đến từ việc chỉ tìm nạp cột đó, SELECT m.id FROM Model msau đó lặp qua Danh sách <Integer>.
Jörn Horstmann

1
@ Jörn Horstmann- nếu có hàng triệu hàng, nó có thực sự quan trọng không? Quan điểm của tôi là một ArrayList với hàng triệu đối tượng (tuy nhiên nhỏ) sẽ không tốt cho JVM heap.
George Armhold

@Dainius: câu hỏi của tôi thực sự là: "làm thế nào tôi có thể lặp lại từng hàng mà không có toàn bộ ArrayList trong bộ nhớ?" Nói cách khác, tôi muốn một giao diện để kéo N tại một thời điểm, trong đó N nhỏ hơn đáng kể 1 triệu. :-)
George Armhold

5

Không có gì "thích hợp" để làm điều này, đây không phải là những gì JPA hoặc JDO hoặc bất kỳ ORM nào khác dự định làm, JDBC thẳng sẽ là sự thay thế tốt nhất của bạn, vì bạn có thể định cấu hình nó để khôi phục một số lượng nhỏ hàng tại một thời gian và xóa chúng khi chúng được sử dụng, đó là lý do tại sao con trỏ phía máy chủ tồn tại.

Các công cụ ORM không được thiết kế để xử lý hàng loạt, chúng được thiết kế để cho phép bạn thao tác các đối tượng và cố gắng làm cho RDBMS mà dữ liệu được lưu trữ trong suốt nhất có thể, hầu hết đều không thành công ở phần trong suốt ít nhất ở một mức độ nào đó. Ở quy mô này, không có cách nào để xử lý hàng trăm nghìn hàng (Đối tượng), ít hơn nhiều hàng triệu với bất kỳ ORM nào và để nó thực thi trong bất kỳ khoảng thời gian hợp lý nào vì chi phí khởi tạo đối tượng, đơn giản và đơn giản.

Sử dụng công cụ thích hợp. JDBC thẳng và Thủ tục lưu trữ chắc chắn có một vị trí trong năm 2011, đặc biệt là ở những gì họ làm tốt hơn so với các khuôn khổ ORM này.

Rút một triệu bất cứ thứ gì, thậm chí thành một thứ đơn giản List<Integer>sẽ không hiệu quả cho dù bạn thực hiện nó như thế nào. Cách chính xác để thực hiện những gì bạn đang yêu cầu là đơn giản SELECT id FROM table, được đặt thành SERVER SIDE(phụ thuộc vào nhà cung cấp) và con trỏ đến FORWARD_ONLY READ-ONLYvà lặp lại điều đó.

Nếu bạn thực sự đang kéo hàng triệu id để xử lý bằng cách gọi một số máy chủ web với mỗi id, bạn sẽ phải thực hiện một số xử lý đồng thời để điều này chạy trong bất kỳ khoảng thời gian hợp lý nào. Kéo bằng con trỏ JDBC và đặt một vài trong số chúng cùng một lúc trong ConcurrentLinkedQueue và có một nhóm nhỏ các luồng (# CPU / Cores + 1) kéo và xử lý chúng là cách duy nhất để hoàn thành tác vụ của bạn trên một máy với bất kỳ " bình thường "dung lượng RAM, vì bạn đã hết bộ nhớ.

Xem câu trả lời này là tốt.


1
Vì vậy, bạn đang nói rằng không có công ty nào cần phải truy cập vào mọi hàng trong bảng người dùng của họ? Các lập trình viên của họ chỉ ném Hibernate ra ngoài cửa sổ khi đến lúc thực hiện việc này? " không có cách nào để xử lý hàng trăm nghìn hàng " - trong câu hỏi của tôi, tôi đã chỉ ra setFirstResult / setMaxResult, vì vậy rõ ràng có một cách. Tôi đang hỏi nếu có một cái tốt hơn.
George Armhold

"Việc kéo một triệu bất kỳ thứ gì, ngay cả vào một Danh sách đơn giản <Integer> sẽ không hiệu quả bất kể bạn làm như thế nào." Đây đúng là ý của tôi! Tôi đang hỏi làm thế nào để không tạo danh sách khổng lồ, mà là lặp lại một tập kết quả.
George Armhold

Sử dụng câu lệnh chọn JDBC thẳng đơn giản với FORWARD_ONLY READ_ONLY với con trỏ SERVER_SIDE như tôi đã đề xuất trong câu trả lời của mình. Cách làm cho JDBC sử dụng con trỏ SERVER_SIDE phụ thuộc vào trình điều khiển cơ sở dữ liệu.

1
Tôi hoàn toàn đồng ý với câu trả lời. Giải pháp tốt nhất là phụ thuộc vào vấn đề. Nếu vấn đề là tải một vài thực thể một cách dễ dàng thì JPA là tốt. Nếu vấn đề là sử dụng một lượng lớn dữ liệu hiệu quả thì JDBC trực tiếp tốt hơn.
extraneon

4
Việc quét qua hàng triệu bản ghi rất phổ biến vì một số lý do, chẳng hạn như lập chỉ mục chúng vào một công cụ tìm kiếm. Và mặc dù tôi đồng ý rằng JDBC thường là một con đường trực tiếp hơn, đôi khi bạn bước vào một dự án đã có logic nghiệp vụ rất phức tạp được đóng gói trong một lớp Hibernate. Nếu bạn bỏ qua nó và truy cập JDBC, bạn đã bỏ qua logic nghiệp vụ, đôi khi không phải là điều tầm thường để thực hiện lại và duy trì. Khi mọi người đăng câu hỏi về các trường hợp sử dụng không điển hình, họ thường biết rằng nó hơi kỳ lạ, nhưng có thể đang kế thừa thứ gì đó so với xây dựng từ đầu và có thể không thể tiết lộ chi tiết.
Mark Bennett

4

Bạn có thể sử dụng một "thủ thuật" khác. Chỉ tải tập hợp số nhận dạng của các thực thể bạn quan tâm. Giả sử số nhận dạng thuộc loại long = 8byte, thì 10 ^ 6 một danh sách các số nhận dạng như vậy có khoảng 8Mb. Nếu đó là một quy trình hàng loạt (một trường hợp tại một thời điểm), thì nó có thể chịu được. Sau đó, chỉ cần lặp lại và thực hiện công việc.

Một nhận xét khác - dù sao thì bạn cũng nên làm điều này theo từng phần - đặc biệt là nếu bạn sửa đổi bản ghi, nếu không thì khôi phục phân đoạn trong cơ sở dữ liệu sẽ phát triển.

Khi nói đến đặt chiến lược firstResult / maxRows - nó sẽ RẤT RẤT chậm đối với các kết quả ở xa đầu.

Cũng cần lưu ý rằng cơ sở dữ liệu có thể đang hoạt động trong trạng thái cách ly được cam kết đọc , do đó, để tránh ảo đọc các mã nhận dạng tải và sau đó tải từng thực thể một (hoặc 10 x 10 hoặc bất cứ thứ gì).


Xin chào @Marcin, bạn hoặc bất kỳ ai khác có thể cung cấp liên kết đến mã ví dụ áp dụng phương pháp tiếp cận theo từng bước phân đoạn và id-đầu tiên này, tốt nhất là sử dụng các luồng Java8 không?
krevelen 14/09/2016

2

Tôi rất ngạc nhiên khi thấy rằng việc sử dụng các thủ tục được lưu trữ không nổi bật hơn trong các câu trả lời ở đây. Trước đây, khi tôi phải làm điều gì đó như thế này, tôi tạo một thủ tục được lưu trữ để xử lý dữ liệu theo từng phần nhỏ, sau đó ngủ một chút rồi tiếp tục. Lý do cho việc ngủ là để không lấn át cơ sở dữ liệu mà có lẽ cũng đang được sử dụng cho nhiều loại truy vấn thời gian thực hơn, chẳng hạn như được kết nối với một trang web. Nếu không có ai khác đang sử dụng cơ sở dữ liệu, thì bạn có thể tạm dừng. Nếu bạn cần đảm bảo rằng bạn xử lý từng bản ghi một lần và chỉ một lần, thì bạn sẽ cần tạo một bảng (hoặc trường) bổ sung để lưu trữ những bản ghi mà bạn đã xử lý để có thể phục hồi khi khởi động lại.

Hiệu suất tiết kiệm ở đây là đáng kể, có thể là các đơn hàng có cường độ nhanh hơn bất cứ thứ gì bạn có thể làm trong vùng đất JPA / Hibernate / AppServer và máy chủ cơ sở dữ liệu của bạn rất có thể sẽ có loại cơ chế con trỏ phía máy chủ riêng để xử lý các tập kết quả lớn một cách hiệu quả. Hiệu suất tiết kiệm đến từ việc không phải chuyển dữ liệu từ máy chủ cơ sở dữ liệu đến máy chủ ứng dụng, nơi bạn xử lý dữ liệu và sau đó gửi lại.

Có một số nhược điểm đáng kể của việc sử dụng các quy trình được lưu trữ có thể loại trừ hoàn toàn điều này đối với bạn, nhưng nếu bạn có kỹ năng đó trong hộp công cụ cá nhân của mình và có thể sử dụng nó trong loại tình huống này, bạn có thể loại bỏ những loại này khá nhanh chóng .


1
-2 phiếu phản đối - người phản đối tiếp theo có vui lòng bảo vệ phiếu phản đối của bạn không?
Nguy hiểm

1
Tôi đã nghĩ điều tương tự khi đọc những điều này. Câu hỏi chỉ ra một công việc hàng loạt khối lượng lớn mà không có giao diện người dùng. Giả sử rằng bạn không cần tài nguyên cụ thể của máy chủ ứng dụng, tại sao lại sử dụng máy chủ ứng dụng? Thủ tục lưu trữ sẽ hiệu quả hơn nhiều.
jdessey

@jdessey Tùy thuộc vào tình huống, giả sử chúng ta có một cơ sở nhập khẩu, nơi nhập khẩu nó sẽ làm điều gì đó với một số phần khác của hệ thống, ví dụ: thêm hàng vào bảng khác dựa trên một số quy tắc nghiệp vụ đã được mã hóa dưới dạng EJB. Sau đó, chạy trong một máy chủ ứng dụng sẽ có ý nghĩa hơn, trừ khi bạn có thể khiến EJB chạy ở chế độ nhúng.
Archimedes Trajano

1

Để mở rộng câu trả lời của @Tomasz Nurkiewicz. Bạn có quyền truy cập đến DataSourcelượt nó có thể cung cấp cho bạn kết nối

@Resource(name = "myDataSource",
    lookup = "java:comp/DefaultDataSource")
private DataSource myDataSource;

Trong mã của bạn, bạn có

try (Connection connection = myDataSource.getConnection()) {
    // raw jdbc operations
}

Điều này sẽ cho phép bạn bỏ qua JPA đối với một số hoạt động hàng loạt lớn cụ thể như nhập / xuất, tuy nhiên bạn vẫn có quyền truy cập vào trình quản lý thực thể cho các hoạt động JPA khác nếu bạn cần.


0

Sử dụng PaginationKhái niệm để truy xuất kết quả


4
Phân trang rất tốt cho GUI. Nhưng để xử lý một lượng lớn dữ liệu, ScrollableResultSet đã được phát minh từ rất lâu trước đây. Nó không phải ở JPA.
extraneon

0

Tôi đã tự hỏi điều này chính mình. Nó có vẻ quan trọng:

  • tập dữ liệu của bạn lớn như thế nào (các hàng)
  • bạn đang sử dụng cách triển khai JPA nào
  • loại xử lý bạn đang thực hiện cho mỗi hàng.

Tôi đã viết một Iterator để giúp dễ dàng hoán đổi cả hai cách tiếp cận (findAll so với findEntries).

Tôi khuyên bạn nên thử cả hai.

Long count = entityManager().createQuery("select count(o) from Model o", Long.class).getSingleResult();
ChunkIterator<Model> it1 = new ChunkIterator<Model>(count, 2) {

    @Override
    public Iterator<Model> getChunk(long index, long chunkSize) {
        //Do your setFirst and setMax here and return an iterator.
    }

};

Iterator<Model> it2 = List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList().iterator();


public static abstract class ChunkIterator<T> 
    extends AbstractIterator<T> implements Iterable<T>{
    private Iterator<T> chunk;
    private Long count;
    private long index = 0;
    private long chunkSize = 100;

    public ChunkIterator(Long count, long chunkSize) {
        super();
        this.count = count;
        this.chunkSize = chunkSize;
    }

    public abstract Iterator<T> getChunk(long index, long chunkSize);

    @Override
    public Iterator<T> iterator() {
        return this;
    }

    @Override
    protected T computeNext() {
        if (count == 0) return endOfData();
        if (chunk != null && chunk.hasNext() == false && index >= count) 
            return endOfData();
        if (chunk == null || chunk.hasNext() == false) {
            chunk = getChunk(index, chunkSize);
            index += chunkSize;
        }
        if (chunk == null || chunk.hasNext() == false) 
            return endOfData();
        return chunk.next();
    }

}

Cuối cùng tôi đã không sử dụng trình lặp chunk của mình (vì vậy nó có thể không được thử nghiệm). Bằng cách này, bạn sẽ cần bộ sưu tập của google nếu bạn muốn sử dụng nó.


Về "loại xử lý bạn đang thực hiện cho mỗi hàng" - nếu số hàng là hàng triệu, tôi nghi ngờ rằng ngay cả một đối tượng đơn giản chỉ có một cột id cũng sẽ gây ra vấn đề. Tôi cũng đã nghĩ về việc viết Iterator của riêng mình bao bọc setFirstResult / setMaxResult, nhưng tôi nhận ra rằng đây phải là một vấn đề phổ biến (và hy vọng đã được giải quyết!).
George Armhold

@Caffeine Coma Tôi đã đăng Iterator của mình, bạn có thể thực hiện thêm một số JPA thích ứng với nó. Hãy cho tôi biết nếu nó có ích. Tôi đã không sử dụng (đã tìm thấy tất cả).
Adam Gent

0

Với chế độ ngủ đông, có 4 cách khác nhau để đạt được điều bạn muốn. Mỗi thiết kế đều có sự cân bằng, hạn chế và hậu quả trong thiết kế. Tôi khuyên bạn nên khám phá từng cái và quyết định cái nào phù hợp với tình huống của bạn.

  1. Sử dụng phiên không trạng thái với scroll ()
  2. Sử dụng session.clear () sau mỗi lần lặp. Khi các thực thể khác cần được đính kèm, hãy tải chúng trong một phiên riêng biệt. hiệu quả là phiên đầu tiên mô phỏng phiên không trạng thái, nhưng vẫn giữ lại tất cả các tính năng của phiên trạng thái, cho đến khi các đối tượng được tách ra.
  3. Sử dụng iterate () hoặc list () nhưng chỉ nhận id trong truy vấn đầu tiên, sau đó trong một phiên riêng biệt trong mỗi lần lặp, hãy thực hiện session.load và đóng phiên khi kết thúc lần lặp.
  4. Sử dụng Query.iterate () với EntityManager.detach () aka Session.evict ();

0

Đây là một ví dụ JPA đơn giản, đơn giản (trong Kotlin) cho thấy cách bạn có thể phân trang trên một tập hợp kết quả lớn tùy ý, đọc các khối 100 mục cùng một lúc mà không cần sử dụng con trỏ (mỗi con trỏ tiêu thụ tài nguyên trên cơ sở dữ liệu). Nó sử dụng phân trang bộ phím.

Xem https://use-the-index-luke.com/no-offset để biết khái niệm về phân trang bộ phím và https://www.citusdata.com/blog/2016/03/30/five-ways-to- phân trang / để so sánh các cách khác nhau để phân trang cùng với nhược điểm của chúng.

/*
create table my_table(
  id int primary key, -- index will be created
  my_column varchar
)
*/

fun keysetPaginationExample() {
    var lastId = Integer.MIN_VALUE
    do {

        val someItems =
        myRepository.findTop100ByMyTableIdAfterOrderByMyTableId(lastId)

        if (someItems.isEmpty()) break

        lastId = someItems.last().myTableId

        for (item in someItems) {
          process(item)
        }

    } while (true)
}

0

Một ví dụ với JPA và NativeQuery luôn tìm nạp các Phần tử kích thước bằng cách sử dụng hiệu số

public List<X> getXByFetching(int fetchSize) {
        int totalX = getTotalRows(Entity);
        List<X> result = new ArrayList<>();
        for (int offset = 0; offset < totalX; offset = offset + fetchSize) {
            EntityManager entityManager = getEntityManager();
            String sql = getSqlSelect(Entity) + " OFFSET " + offset + " ROWS";
            Query query = entityManager.createNativeQuery(sql, X.class);
            query.setMaxResults(fetchSize);
            result.addAll(query.getResultList());
            entityManager.flush();
            entityManager.clear();
        return result;
    }
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.