JPA: pola apa yang tepat untuk melakukan iterasi pada kumpulan hasil yang besar?

114

Katakanlah saya memiliki tabel dengan jutaan baris. Menggunakan JPA, apa cara yang tepat untuk mengulangi kueri terhadap tabel itu, sehingga saya tidak memiliki semua Daftar dalam memori dengan jutaan objek?

Misalnya, saya menduga bahwa hal berikut akan meledak jika mejanya besar:

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

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

Apakah pagination (perulangan dan pembaruan setFirstResult()/ secara manual setMaxResult()) benar-benar solusi terbaik?

Sunting : kasus penggunaan utama yang saya targetkan adalah jenis pekerjaan batch. Tidak masalah jika butuh waktu lama untuk menjalankannya. Tidak ada klien web yang terlibat; Saya hanya perlu "melakukan sesuatu" untuk setiap baris, satu (atau beberapa N kecil) pada satu waktu. Saya hanya mencoba untuk menghindari semuanya dalam memori pada saat yang bersamaan.

George Armhold
sumber
Database dan driver JDBC apa yang Anda gunakan?

Jawaban:

55

Halaman 537 dari Java Persistence with Hibernate memberikan solusi menggunakan ScrollableResults, tapi sayangnya itu hanya untuk Hibernate.

Jadi sepertinya penggunaan setFirstResult/ setMaxResultsdan iterasi manual memang sangat diperlukan. Inilah solusi saya menggunakan JPA:

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

lalu, gunakan seperti ini:

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();
    }
}
George Armhold
sumber
33
Saya pikir contohnya tidak aman jika ada sisipan baru selama proses batch. Pengguna harus memesan berdasarkan kolom yang sudah dipastikan data yang baru disisipkan akan berada di akhir daftar hasil.
Balazs Zsoldos
ketika halaman saat ini adalah halaman terakhir dan memiliki kurang dari 100 elemen yang diperiksa size() == 100akan melewatkan satu kueri tambahan yang mengembalikan daftar kosong
cdalxndr
38

Saya mencoba jawaban yang disajikan di sini, tetapi JBoss 5.1 + Konektor MySQL / J 5.1.15 + Hibernate 3.3.2 tidak berfungsi dengan itu. Kami baru saja bermigrasi dari JBoss 4.x ke JBoss 5.1, jadi kami tetap menggunakannya untuk saat ini, dan Hibernate terbaru yang dapat kami gunakan adalah 3.3.2.

Menambahkan beberapa parameter ekstra berhasil, dan kode seperti ini berjalan tanpa 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();

Garis penting adalah parameter kueri antara createQuery dan scroll. Tanpa mereka panggilan "scroll" mencoba memuat semuanya ke dalam memori dan tidak pernah selesai atau berjalan ke OutOfMemoryError.

Zds
sumber
2
Hai Zds, kasus penggunaan Anda untuk memindai jutaan baris tentu saja biasa bagi saya, dan TERIMA KASIH telah memposting kode terakhir. Dalam kasus saya, saya mendorong catatan ke Solr, untuk mengindeksnya untuk pencarian teks lengkap. Dan, karena aturan bisnis yang tidak akan saya bahas, saya harus menggunakan Hibernate, vs. hanya menggunakan modul bawaan JDBC atau Solr.
Mark Bennett
Senang membantu :-). Kami juga berurusan dengan kumpulan data yang besar, dalam hal ini memungkinkan pengguna untuk menanyakan semua nama jalan dalam kota / kabupaten yang sama, atau terkadang bahkan negara bagian, sehingga membuat indeks membutuhkan banyak membaca data.
Zds
Muncul dengan MySQL Anda benar-benar harus melalui semua rintangan itu: stackoverflow.com/a/20900045/32453 (DB lain mungkin kurang ketat menurut saya ...)
rogerdpack
32

Anda tidak dapat benar-benar melakukan ini dalam JPA langsung, namun Hibernate memiliki dukungan untuk sesi tanpa negara dan rangkaian hasil yang dapat digulir.

Kami secara rutin memproses miliaran baris dengan bantuannya.

Berikut ini tautan ke dokumentasi: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession

Cyberax
sumber
17
Terima kasih. Senang rasanya mengetahui seseorang melakukan miliaran baris melalui Hibernate. Beberapa orang di sini mengatakan itu tidak mungkin. :-)
George Armhold
2
Mungkinkah menambahkan contoh di sini juga? Saya berasumsi itu mirip dengan contoh Zds?
rogerdpack
19

Sejujurnya, saya menyarankan untuk meninggalkan JPA dan tetap menggunakan JDBC (tapi tentunya menggunakan JdbcTemplatekelas dukungan atau semacamnya). JPA (dan penyedia / spesifikasi ORM lainnya) tidak dirancang untuk beroperasi pada banyak objek dalam satu transaksi karena mereka menganggap semua yang dimuat harus tetap berada di cache tingkat pertama (oleh karena itu diperlukan clear()di JPA).

Juga saya merekomendasikan solusi tingkat yang lebih rendah karena overhead ORM (refleksi hanya puncak gunung es) mungkin sangat signifikan, sehingga iterasi di atas dataran ResultSet, bahkan menggunakan beberapa dukungan ringan seperti yang disebutkan JdbcTemplateakan jauh lebih cepat.

JPA tidak dirancang untuk melakukan operasi pada sejumlah besar entitas. Anda mungkin bermain dengan flush()/ clear()untuk menghindari OutOfMemoryError, tapi pertimbangkan ini sekali lagi. Anda mendapat sedikit keuntungan dari harga konsumsi sumber daya yang besar.

Tomasz Nurkiewicz
sumber
Keuntungan JPA bukan hanya database agnostik tetapi kemungkinan bahkan tidak menggunakan database tradisional (NoSQL). Tidak sulit untuk melakukan flush / clear sesekali dan biasanya operasi batch jarang dilakukan.
Adam Gent
1
Hai Thomasz. Saya punya banyak alasan untuk mengeluh tentang JPA / Hibernate, tetapi dengan hormat, saya benar-benar ragu bahwa mereka "tidak dirancang untuk beroperasi pada banyak objek". Saya menduga bahwa saya hanya perlu mempelajari pola yang tepat untuk kasus penggunaan ini.
George Armhold
4
Saya hanya dapat memikirkan dua pola: paginasi (disebutkan beberapa kali) dan flush()/ clear(). Yang pertama adalah IMHO tidak dirancang untuk keperluan pemrosesan batch, sedangkan menggunakan urutan flush () / clear () yang berbau seperti abstraksi bocor .
Tomasz Nurkiewicz
Yup, itu adalah kombinasi dari pagination dan flush / clear seperti yang Anda sebutkan. Terima kasih!
George Armhold
7

Jika Anda menggunakan EclipseLink I 'menggunakan metode ini untuk mendapatkan hasil sebagai Iterable

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

Tutup Metode

static void closeCursor(Iterable<?> list)
{
  if (list.iterator() instanceof Cursor)
    {
      ((Cursor) list.iterator()).close();
    }
}
pengguna2008477
sumber
6
Objek jQuery yang bagus
usr-local-ΕΨΗΕΛΩΝ
Saya mencoba kode Anda tetapi masih mendapatkan OOM - tampaknya semua objek T (dan semua objek tabel gabungan yang dirujuk dari T) tidak pernah GC. Profil menunjukkan mereka dirujuk dari "tabel" di org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork bersama dengan org.eclipse.persistence.internal.identitymaps.CacheKey. Saya melihat ke cache dan pengaturan saya semuanya default (Nonaktifkan Selektif, Lemah dengan Soft Subcache, Cache Size 100, Drop Invalidate). Saya akan memeriksa sesi penonaktifan dan melihat apakah itu membantu. BTW Saya hanya mengulangi kursor kembali menggunakan "untuk (T o: hasil)".
Edi Bice
Badum tssssssss
dctremblay
5

Itu tergantung pada jenis operasi yang harus Anda lakukan. Mengapa Anda mengulang lebih dari satu juta baris? Apakah Anda memperbarui sesuatu dalam mode batch? Apakah Anda akan menampilkan semua catatan ke klien? Apakah Anda menghitung beberapa statistik atas entitas yang diambil?

Jika Anda ingin menampilkan sejuta catatan ke klien, harap pertimbangkan kembali antarmuka pengguna Anda. Dalam kasus ini, solusi yang tepat adalah memberi nomor pada hasil Anda dan menggunakan setFirstResult()dan setMaxResult().

Jika Anda telah meluncurkan pembaruan sejumlah besar catatan, sebaiknya perbarui tetap sederhana dan digunakan Query.executeUpdate(). Secara opsional, Anda dapat menjalankan pembaruan dalam mode asynchronous menggunakan Message-Driven Bean oa Work Manager.

Jika Anda menghitung beberapa statistik pada entitas yang diambil, Anda dapat memanfaatkan fungsi pengelompokan yang ditentukan oleh spesifikasi JPA.

Untuk kasus lain, harap lebih spesifik :)

frm
sumber
Sederhananya, saya perlu melakukan sesuatu "untuk setiap" baris. Tentunya ini adalah kasus penggunaan yang umum. Dalam kasus khusus yang saya kerjakan sekarang, saya perlu menanyakan layanan web eksternal yang benar-benar berada di luar database saya, menggunakan id (PK) dari setiap baris. Hasilnya tidak ditampilkan kembali ke browser web klien mana pun, jadi tidak ada antarmuka pengguna untuk dibicarakan. Dengan kata lain, ini adalah pekerjaan batch.
George Armhold
Jika Anda "membutuhkan" print id untuk setiap baris, tidak ada cara lain seperti mendapatkan setiap baris, dapatkan id dan cetak. Solusi terbaik tergantung pada apa yang perlu Anda lakukan.
Dainius
@Caffeine Coma, jika Anda hanya membutuhkan id dari setiap baris maka peningkatan terbesar mungkin akan datang dari hanya mengambil kolom itu, SELECT m.id FROM Model mdan kemudian melakukan iterasi ke List <Integer>.
Jörn Horstmann
1
@ Jörn Horstmann- jika ada jutaan baris, apakah itu penting? Maksud saya adalah bahwa ArrayList dengan jutaan objek (betapapun kecilnya) tidak akan baik untuk heap JVM.
George Armhold
@Dainius: pertanyaan saya sebenarnya: "bagaimana saya dapat mengulang setiap baris, tanpa seluruh ArrayList dalam memori?" Dengan kata lain, saya ingin antarmuka untuk menarik N pada satu waktu, di mana N secara signifikan lebih kecil dari 1 juta. :-)
George Armhold
5

Tidak ada yang "tepat" untuk melakukan ini, ini bukan yang dimaksudkan JPA atau JDO atau ORM lainnya, JDBC langsung akan menjadi alternatif terbaik Anda, karena Anda dapat mengonfigurasinya untuk mengembalikan sejumlah kecil baris di waktu dan hapus mereka saat digunakan, itulah sebabnya kursor sisi server ada.

Alat ORM tidak dirancang untuk pemrosesan massal, alat ini dirancang untuk memungkinkan Anda memanipulasi objek dan mencoba membuat RDBMS tempat data disimpan setransparan mungkin, sebagian besar gagal pada bagian transparan setidaknya sampai tingkat tertentu. Pada skala ini, tidak ada cara untuk memproses ratusan ribu baris (Objek), apalagi jutaan dengan ORM apa pun dan membuatnya dieksekusi dalam jumlah waktu yang wajar karena overhead instantiation objek, polos dan sederhana.

Gunakan alat yang sesuai. Straight JDBC dan Stored Procedures pasti memiliki tempat di tahun 2011, terutama pada apa yang mereka lakukan dengan lebih baik dibandingkan kerangka ORM ini.

Menarik jutaan hal, bahkan menjadi yang sederhana List<Integer>tidak akan menjadi sangat efisien terlepas dari bagaimana Anda melakukannya. Cara yang benar untuk melakukan apa yang Anda minta adalah sederhana SELECT id FROM table, setel ke SERVER SIDE(tergantung vendor) dan kursor ke FORWARD_ONLY READ-ONLYdan ulangi di atasnya.

Jika Anda benar-benar menarik jutaan id untuk diproses dengan memanggil beberapa server web dengan masing-masing, Anda harus melakukan beberapa pemrosesan bersamaan juga agar ini berjalan dalam jumlah waktu yang wajar. Menarik dengan kursor JDBC dan menempatkan beberapa di antaranya sekaligus di ConcurrentLinkedQueue dan memiliki kumpulan kecil utas (# CPU / Cores + 1) menarik dan memprosesnya adalah satu-satunya cara untuk menyelesaikan tugas Anda di mesin dengan " normal "jumlah RAM, mengingat Anda sudah kehabisan memori.

Lihat jawaban ini juga.

Komunitas
sumber
1
Jadi maksud Anda tidak ada perusahaan yang perlu mengunjungi setiap baris tabel pengguna mereka? Programer mereka hanya membuang Hibernate ke luar jendela ketika tiba saatnya untuk melakukan ini? " Tidak ada cara untuk ratusan proses ribu baris " - dalam pertanyaan saya saya menunjukkan setFirstResult / setMaxResult, sehingga jelas ada adalah jalan. Saya bertanya apakah ada yang lebih baik.
George Armhold
"Menarik sejuta hal, bahkan ke dalam List <Integer> sederhana tidak akan menjadi sangat efisien terlepas dari bagaimana Anda melakukannya." Itulah poin saya. Saya bertanya bagaimana tidak membuat daftar raksasa, melainkan untuk mengulangi set hasil.
George Armhold
Gunakan pernyataan pemilihan JDBC lurus sederhana dengan FORWARD_ONLY READ_ONLY dengan kursor SERVER_SIDE seperti yang saya sarankan dalam jawaban saya. Cara membuat JDBC menggunakan kursor SERVER_SIDE tergantung pada driver database.
1
Saya setuju sepenuhnya dengan jawabannya. Solusi terbaik tergantung pada masalahnya. Jika masalahnya memuat beberapa entitas dengan mudah, JPA bagus. Jika masalahnya adalah menggunakan data dalam jumlah besar secara efisien mengarahkan JDBC lebih baik.
ekstraneon
4
Memindai jutaan rekaman adalah hal biasa karena beberapa alasan, misalnya mengindeksnya ke mesin pencari. Dan meskipun saya setuju bahwa JDBC biasanya merupakan rute yang lebih langsung, terkadang Anda masuk ke proyek yang sudah memiliki logika bisnis yang sangat kompleks yang digabungkan dalam lapisan Hibernate. Jika Anda melewati dan pergi ke JDBC, Anda melewati logika bisnis, yang terkadang tidak sepele untuk diterapkan dan dipelihara kembali. Saat orang-orang memposting pertanyaan tentang kasus penggunaan yang tidak biasa, mereka sering tahu itu agak aneh, tetapi mungkin mewarisi sesuatu vs. membangun dari awal, dan mungkin tidak dapat mengungkapkan detailnya.
Mark Bennett
4

Anda bisa menggunakan "trik" lain. Muat hanya kumpulan pengenal dari entitas yang Anda minati. Misalkan pengenal berjenis long = 8bytes, maka 10 ^ 6 daftar pengenal tersebut menghasilkan sekitar 8 MB. Jika ini adalah proses batch (satu instance pada satu waktu), maka itu bisa diterima. Kemudian lakukan iterasi saja dan lakukan pekerjaan itu.

Satu komentar lain - Anda tetap harus melakukan ini dalam potongan - terutama jika Anda memodifikasi record, jika tidak segmen rollback dalam database akan bertambah.

Ketika datang untuk mengatur strategi firstResult / maxRows - itu akan SANGAT SANGAT lambat untuk hasil yang jauh dari atas.

Juga pertimbangkan bahwa database mungkin beroperasi dalam isolasi baca komit , jadi untuk menghindari pengenal beban baca bayangan dan kemudian memuat entitas satu per satu (atau 10 kali 10 atau apa pun).

Marcin Cinik
sumber
Hai @Marcin, dapatkah Anda atau orang lain memberikan tautan ke contoh kode yang menerapkan pendekatan bertahap yang dipotong dan id-pertama ini, lebih disukai menggunakan aliran Java8?
krevelen
2

Saya terkejut melihat bahwa penggunaan prosedur tersimpan tidak lebih menonjol dalam jawaban di sini. Di masa lalu ketika saya harus melakukan sesuatu seperti ini, saya membuat prosedur tersimpan yang memproses data dalam potongan kecil, lalu tidur sebentar, lalu melanjutkan. Alasan tidurnya adalah untuk tidak membanjiri database yang mungkin juga digunakan untuk jenis kueri yang lebih real time, seperti terhubung ke situs web. Jika tidak ada orang lain yang menggunakan database, maka Anda tidak perlu tidur. Jika Anda perlu memastikan bahwa Anda memproses setiap rekaman sekali dan hanya sekali, maka Anda perlu membuat tabel tambahan (atau bidang) untuk menyimpan rekaman mana yang telah Anda proses agar tangguh saat dimulai ulang.

Penghematan kinerja di sini signifikan, mungkin lipat lebih cepat daripada apa pun yang dapat Anda lakukan di tanah JPA / Hibernate / AppServer, dan server database Anda kemungkinan besar akan memiliki jenis mekanisme kursor sisi server sendiri untuk memproses kumpulan hasil besar secara efisien. Penghematan kinerja berasal dari tidak harus mengirimkan data dari server database ke server aplikasi, tempat Anda memproses data, dan kemudian mengirimkannya kembali.

Ada beberapa kerugian signifikan dalam menggunakan prosedur tersimpan yang mungkin sepenuhnya mengesampingkan hal ini untuk Anda, tetapi jika Anda memiliki keterampilan itu di kotak peralatan pribadi Anda dan dapat menggunakannya dalam situasi seperti ini, Anda dapat melumpuhkan hal-hal semacam ini dengan cukup cepat. .

Bahaya
sumber
1
-2 suara negatif - apakah suara negatif berikutnya akan mempertahankan suara negatif Anda?
Bahaya
1
Saya memikirkan hal yang sama saat membaca ini. Pertanyaannya menunjukkan pekerjaan batch volume tinggi tanpa UI. Dengan asumsi bahwa Anda tidak memerlukan sumber daya khusus server aplikasi, mengapa menggunakan server aplikasi sama sekali? Prosedur yang disimpan akan jauh lebih efisien.
jdessey
@jdessey Bergantung pada situasinya, katakanlah kita memiliki fasilitas impor di mana pada impor itu harus melakukan sesuatu dengan beberapa bagian lain dari sistem misalnya menambahkan baris ke tabel lain berdasarkan beberapa aturan bisnis yang telah dikodekan sebagai EJB. Kemudian menjalankan di server aplikasi akan lebih masuk akal, kecuali Anda bisa menjalankan EJB dalam mode tertanam.
Archimedes Trajano
1

Untuk memperluas jawaban @Tomasz Nurkiewicz. Anda memiliki akses DataSourceyang pada gilirannya dapat memberi Anda koneksi

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

Dalam kode Anda, Anda punya

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

Ini akan memungkinkan Anda untuk melewati JPA untuk beberapa operasi batch besar tertentu seperti impor / ekspor, namun Anda masih memiliki akses ke pengelola entitas untuk operasi JPA lainnya jika Anda membutuhkannya.

Archimedes Trajano
sumber
0

Gunakan PaginationKonsep untuk mengambil hasil

Programmer Mati
sumber
4
Penomoran halaman sangat bagus untuk GUI. Tetapi untuk memproses data dalam jumlah besar, ScrollableResultSet telah ditemukan sejak lama. Hanya saja tidak di JPA.
ekstraneon
0

Saya sendiri yang bertanya-tanya tentang ini. Tampaknya penting:

  • seberapa besar kumpulan data Anda (baris)
  • implementasi JPA apa yang Anda gunakan
  • jenis pemrosesan apa yang Anda lakukan untuk setiap baris.

Saya telah menulis sebuah Iterator untuk mempermudah menukar kedua pendekatan (findAll vs findEntries).

Saya sarankan Anda mencoba keduanya.

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

}

Saya akhirnya tidak menggunakan iterator potongan saya (jadi itu mungkin tidak bisa diuji). Ngomong-ngomong, Anda akan membutuhkan koleksi google jika ingin menggunakannya.

Adam Gent
sumber
Mengenai "jenis pemrosesan apa yang Anda lakukan untuk setiap baris" - jika # baris dalam jutaan, saya menduga bahwa objek sederhana dengan hanya kolom id akan menyebabkan masalah. Saya juga berpikir untuk menulis Iterator saya sendiri yang membungkus setFirstResult / setMaxResult, tetapi saya pikir ini pasti masalah umum (dan mudah-mudahan bisa diselesaikan!).
George Armhold
@Caffeine Coma Saya memposting Iterator saya, Anda mungkin dapat melakukan lebih banyak JPA beradaptasi dengannya. Beritahu saya jika itu membantu. Saya akhirnya tidak menggunakan (melakukan findAll).
Adam Gent
0

Dengan hibernate, ada 4 cara berbeda untuk mencapai apa yang Anda inginkan. Masing-masing memiliki pengorbanan desain, batasan, dan konsekuensi. Saya sarankan untuk menjelajahi masing-masing dan memutuskan mana yang tepat untuk situasi Anda.

  1. Gunakan sesi stateless dengan scroll ()
  2. Gunakan session.clear () setelah setiap iterasi. Saat entitas lain perlu dilampirkan, muatlah dalam sesi terpisah. efektif sesi pertama meniru sesi stateless, tetapi mempertahankan semua fitur sesi stateful, hingga objek terlepas.
  3. Gunakan iterate () atau list () tetapi dapatkan hanya id di kueri pertama, lalu di sesi terpisah di setiap iterasi, lakukan session.load dan tutup sesi di akhir iterasi.
  4. Gunakan Query.iterate () dengan EntityManager.detach () alias Session.evict ();
Larry Chu
sumber
0

Berikut adalah contoh JPA langsung yang sederhana (di Kotlin) yang menunjukkan bagaimana Anda dapat memberi nomor pada kumpulan hasil yang sangat besar, membaca potongan 100 item sekaligus, tanpa menggunakan kursor (setiap kursor menghabiskan sumber daya pada database). Ini menggunakan pagination keyset.

Lihat https://use-the-index-luke.com/no-offset untuk konsep pagination keyset, dan https://www.citusdata.com/blog/2016/03/30/five-ways-to- paginate / untuk perbandingan berbagai cara untuk membuat halaman beserta kekurangannya.

/*
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)
}
Elifarley
sumber
0

Contoh dengan JPA dan NativeQuery yang mengambil setiap elemen ukuran menggunakan offset

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;
    }
harryssuperman.dll
sumber