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.
Jawaban:
Halaman 537 dari Java Persistence with Hibernate memberikan solusi menggunakan
ScrollableResults
, tapi sayangnya itu hanya untuk Hibernate.Jadi sepertinya penggunaan
setFirstResult
/setMaxResults
dan iterasi manual memang sangat diperlukan. Inilah solusi saya menggunakan JPA:lalu, gunakan seperti ini:
sumber
size() == 100
akan melewatkan satu kueri tambahan yang mengembalikan daftar kosongSaya 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:
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.
sumber
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
sumber
Sejujurnya, saya menyarankan untuk meninggalkan JPA dan tetap menggunakan JDBC (tapi tentunya menggunakan
JdbcTemplate
kelas 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 diperlukanclear()
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 disebutkanJdbcTemplate
akan jauh lebih cepat.JPA tidak dirancang untuk melakukan operasi pada sejumlah besar entitas. Anda mungkin bermain dengan
flush()
/clear()
untuk menghindariOutOfMemoryError
, tapi pertimbangkan ini sekali lagi. Anda mendapat sedikit keuntungan dari harga konsumsi sumber daya yang besar.sumber
flush()
/clear()
. Yang pertama adalah IMHO tidak dirancang untuk keperluan pemrosesan batch, sedangkan menggunakan urutan flush () / clear () yang berbau seperti abstraksi bocor .Jika Anda menggunakan EclipseLink I 'menggunakan metode ini untuk mendapatkan hasil sebagai Iterable
Tutup Metode
sumber
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()
dansetMaxResult()
.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 :)
sumber
SELECT m.id FROM Model m
dan kemudian melakukan iterasi ke List <Integer>.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 sederhanaSELECT id FROM table
, setel keSERVER SIDE
(tergantung vendor) dan kursor keFORWARD_ONLY READ-ONLY
dan 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.
sumber
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).
sumber
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. .
sumber
Untuk memperluas jawaban @Tomasz Nurkiewicz. Anda memiliki akses
DataSource
yang pada gilirannya dapat memberi Anda koneksiDalam kode Anda, Anda punya
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.
sumber
Gunakan
Pagination
Konsep untuk mengambil hasilsumber
Saya sendiri yang bertanya-tanya tentang ini. Tampaknya penting:
Saya telah menulis sebuah Iterator untuk mempermudah menukar kedua pendekatan (findAll vs findEntries).
Saya sarankan Anda mencoba keduanya.
Saya akhirnya tidak menggunakan iterator potongan saya (jadi itu mungkin tidak bisa diuji). Ngomong-ngomong, Anda akan membutuhkan koleksi google jika ingin menggunakannya.
sumber
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.
sumber
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.
sumber
Contoh dengan JPA dan NativeQuery yang mengambil setiap elemen ukuran menggunakan offset
sumber