Hibernate, @SequenceGenerator, dan alokasiSize

117

Kita semua tahu perilaku default Hibernate saat menggunakan @SequenceGenerator- ini meningkatkan urutan database nyata satu , mengalikan nilai ini dengan 50 ( allocationSizenilai default ) - dan kemudian menggunakan nilai ini sebagai ID entitas.

Ini adalah perilaku yang salah dan bertentangan dengan spesifikasi yang mengatakan:

alokasiSize - (Opsional) Jumlah yang akan ditambahkan saat mengalokasikan nomor urut dari urutan.

Untuk memperjelas: Saya tidak peduli tentang celah antara ID yang dihasilkan.

Saya peduli dengan ID yang tidak konsisten dengan urutan database yang mendasarinya. Misalnya: aplikasi lain (misalnya menggunakan JDBC biasa) mungkin ingin memasukkan baris baru di bawah ID yang diperoleh dari urutan - tetapi semua nilai tersebut mungkin sudah digunakan oleh Hibernate! Kegilaan.

Apakah ada yang tahu solusi untuk masalah ini (tanpa pengaturan allocationSize=1dan dengan demikian menurunkan kinerja)?

EDIT:
Untuk memperjelas. Jika catatan yang terakhir dimasukkan memiliki ID = 1, maka nilai penggunaan HB 51, 52, 53...untuk entitas barunya TETAPI pada saat yang sama: nilai urutan dalam database akan disetel ke 2. Yang dapat dengan mudah menyebabkan kesalahan saat aplikasi lain menggunakan urutan itu.

Di sisi lain: spesifikasi mengatakan (dalam pemahaman saya) bahwa urutan database seharusnya diatur ke 51dan sementara itu HB harus menggunakan nilai dari jangkauan 2, 3 ... 50


PEMBARUAN:
Seperti yang disebutkan Steve Ebersole di bawah ini: perilaku yang saya jelaskan (dan juga yang paling intuitif bagi banyak orang) dapat diaktifkan dengan pengaturan hibernate.id.new_generator_mappings=true.

Terima kasih semuanya.

UPDATE 2:
Untuk pembaca selanjutnya, di bawah ini Anda dapat menemukan contoh yang berfungsi.

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USERS_SEQ")
    @SequenceGenerator(name = "USERS_SEQ", sequenceName = "SEQUENCE_USERS")
    private Long id;
}

persistence.xml

<persistence-unit name="testPU">
  <properties>
    <property name="hibernate.id.new_generator_mappings" value="true" />
  </properties>
</persistence-unit>
G. Demecki
sumber
2
"tanpa mengatur alokasiSize = 1 dan dengan demikian menurunkan kinerja" mengapa itu menurunkan kinerja, apakah Anda menyetelnya ke 1?
sheidaei
3
@sheidaei lihat komentar di bawah :-) Ini karena setiap savekebutuhan untuk query database untuk nilai berikutnya dari urutan.
G. Demecki
Terima kasih menghadapi masalah yang sama. Awalnya saya menambahkan alokasiSize = 1 di setiap @SequenceGenerator. Menggunakan hibernate.id.new_generator_mappings = true mencegahnya. Meskipun JPA masih meminta database untuk mendapatkan id untuk setiap penyisipan ...
TheBakker
1
Dengan SequenceGeneratorHibernate akan meminta database hanya jika jumlah ID yang ditentukan allocationsizehabis. Jika Anda mengatur allocationSize = 1maka itu alasan mengapa Hibernate query DB untuk setiap sisipan. Ubah nilai ini, dan Anda selesai.
G. Demecki
1
Terima kasih! yang hibernate.id.new_generator_mappingspengaturan benar-benar penting. Saya berharap ini adalah pengaturan default yang saya tidak perlu menghabiskan begitu banyak waktu untuk meneliti mengapa nomor id menjadi liar.
LeOn - Han Li

Jawaban:

43

Untuk lebih jelasnya ... apa yang Anda gambarkan tidak bertentangan dengan spesifikasi sama sekali. Spesifikasi berbicara tentang nilai yang diberikan Hibernate ke entitas Anda, bukan nilai yang sebenarnya disimpan dalam urutan database.

Namun, ada opsi untuk mendapatkan perilaku yang Anda cari. Pertama lihat balasan saya di Apakah ada cara untuk secara dinamis memilih strategi @GeneratedValue menggunakan anotasi JPA dan Hibernate? Itu akan memberi Anda dasar-dasarnya. Selama Anda diatur untuk menggunakan SequenceStyleGenerator itu, Hibernate akan menafsirkan allocationSizemenggunakan "pengoptimal gabungan" di SequenceStyleGenerator. The "pooled optimizer" adalah untuk digunakan dengan database yang memungkinkan opsi "increment" pada pembuatan urutan (tidak semua database yang mendukung sequence mendukung increment). Bagaimanapun, baca tentang berbagai strategi pengoptimal di sana.

Steve Ebersole
sumber
Terima kasih Steve! Jawaban terbaik. Juga posting Anda yang lain sangat membantu.
G. Demecki
4
Saya juga memperhatikan bahwa Anda adalah penulis bersama dari org.hibernate.id.enhanced.SequenceStyleGenerator. Anda mengejutkan saya.
G. Demecki
22
Anda terkejut bagaimana? Saya adalah pengembang utama Hibernate. Saya telah menulis / ikut menulis banyak kelas Hibernate;)
Steve Ebersole
Sekadar catatan. Peningkatan urutan DB harus dihindari untuk mencegah celah yang besar. Urutan DB dikalikan dengan alokasiSize ketika cache ID habis. Detail lebih lanjut stackoverflow.com/questions/5346147/…
Olcay Tarazan
1
Salah satu cara untuk mengubah "pengoptimal" yang digunakan secara global adalah dengan menambahkan sesuatu seperti ini ke opsi hibernasi Anda: serviceBuilder.applySetting ("hibernate.id.optimizer.pooled.preferred", LegacyHiLoAlgorithmOptimizer.class.getName ()); Alih-alih LegacyHiLoAlgorithOptimizer, Anda dapat memilih kelas pengoptimal apa pun, dan itu akan menjadi default. Ini akan memudahkan untuk mempertahankan perilaku yang Anda inginkan sebagai default tanpa mengubah semua anotasi. Selain itu, hati-hati dengan pengoptimal "gabungan" dan "hilo": ini memberikan hasil ganjil saat nilai urutan Anda dimulai dari 0 yang menyebabkan ID negatif.
fjalvingh
17

allocationSize=1Ini adalah optimasi mikro sebelum mendapatkan permintaan Hibernate mencoba untuk menetapkan nilai dalam kisaran alokasiSize dan mencoba untuk menghindari database query untuk urutan. Tetapi query ini akan dieksekusi setiap kali jika Anda mengaturnya ke 1. Ini hampir tidak membuat perbedaan karena jika basis data Anda diakses oleh beberapa aplikasi lain maka akan menimbulkan masalah jika id yang sama digunakan oleh aplikasi lain.

Generasi selanjutnya dari Id Urutan didasarkan pada alokasiSize.

Secara defualt disimpan sebagai 50yang terlalu banyak. Ini juga hanya akan membantu jika Anda akan berada di sekitar50 catatan dalam satu sesi yang tidak dipertahankan dan yang akan dipertahankan menggunakan sesi dan transasi khusus ini.

Jadi, Anda harus selalu menggunakan allocationSize=1saat menggunakan SequenceGenerator. Adapun sebagian besar urutan database yang mendasari selalu bertambah 1.

Amit Deshpande
sumber
12
Tidak ada hubungannya dengan kinerja? Apa kamu benar-benar yakin? Saya telah diajarkan bahwa dengan allocationSize=1Hibernate pada setiap saveoperasi perlu melakukan perjalanan ke database untuk mendapatkan nilai ID baru.
G. Demecki
2
Ini adalah optimasi mikro sebelum mendapatkan permintaan Hibernate mencoba untuk menetapkan nilai dalam kisaran allocationSizedan mencoba untuk menghindari database query untuk urutan. Tetapi kueri ini akan dijalankan setiap kali jika Anda menyetelnya ke 1. Ini hampir tidak membuat perbedaan karena jika basis data Anda diakses oleh beberapa aplikasi lain maka akan menimbulkan masalah jika id yang sama digunakan oleh aplikasi lain sementara itu
Amit Deshpande
Dan ya, sepenuhnya khusus aplikasi apakah ukuran alokasi 1 memiliki dampak kinerja yang nyata. Dalam tolok ukur mikro, tentu saja, ini akan selalu muncul sebagai dampak yang sangat besar; itulah masalah dengan sebagian besar tolok ukur (mikro atau sebaliknya), mereka sama sekali tidak realistis. Dan bahkan jika mereka cukup kompleks untuk menjadi agak realistis, Anda masih harus melihat seberapa dekat tolok ukur tersebut dengan aplikasi Anda yang sebenarnya untuk memahami seberapa dapat diterapkan hasil benchmark ke hasil yang akan Anda lihat di aplikasi Anda. Singkat cerita .. ujilah sendiri
Steve Ebersole
2
BAIK. Semuanya spesifik aplikasi, bukan! Jika aplikasi Anda adalah aplikasi hanya-baca, maka dampak penggunaan ukuran alokasi 1000 atau 1 adalah 0. Di sisi lain, hal-hal seperti ini adalah praktik terbaik. Jika Anda tidak menghormati praktik terbaik yang mereka kumpulkan dan dampak gabungannya adalah aplikasi Anda menjadi lamban. Contoh lain adalah memulai transaksi ketika Anda sama sekali tidak membutuhkannya.
Hasan Ceylan
1

Steve Ebersole & anggota lain,
Maukah Anda menjelaskan alasan id dengan celah yang lebih besar (secara default 50)? Saya menggunakan Hibernate 4.2.15 dan menemukan kode berikut di org.hibernate.id.enhanced.OptimizerFactory cass.

if ( lo > maxLo ) {
   lastSourceValue = callback.getNextValue();
   lo = lastSourceValue.eq( 0 ) ? 1 : 0;
   hi = lastSourceValue.copy().multiplyBy( maxLo+1 ); 
}  
value = hi.copy().add( lo++ );

Setiap kali itu mengenai bagian dalam pernyataan if, nilai hi menjadi jauh lebih besar. Jadi, id saya selama pengujian dengan server yang sering restart menghasilkan id urutan berikut:
1, 2, 3, 4, 19, 250, 251, 252, 400, 550, 750, 751, 752, 850, 1100, 1150.

Saya tahu Anda sudah mengatakan itu tidak bertentangan dengan spesifikasi, tetapi saya yakin ini akan menjadi situasi yang sangat tidak terduga bagi sebagian besar pengembang.

Masukan siapa pun akan sangat membantu.

Jihwan

UPDATE: ne1410s: Terima kasih atas pengeditannya.
cfrick: Oke. Saya akan melakukan itu. Itu adalah posting pertama saya di sini dan tidak yakin bagaimana cara menggunakannya.

Sekarang, saya lebih mengerti mengapa maxLo digunakan untuk dua tujuan: Karena hibernate memanggil urutan DB sekali, terus tingkatkan id di level Java, dan simpan ke DB, nilai id level Java harus mempertimbangkan berapa banyak yang berubah tanpa memanggil urutan DB ketika memanggil urutan waktu berikutnya.

Misalnya, id urutan adalah 1 pada satu titik dan hibernasi memasuki 5, 6, 7, 8, 9 (dengan alokasiSize = 5). Lain kali, ketika kita mendapatkan nomor urut berikutnya, DB mengembalikan 2, tetapi hibernasi perlu menggunakan 10, 11, 12 ... Jadi, itulah mengapa "hi = lastSourceValue.copy (). MultiplyBy (maxLo + 1)" adalah digunakan untuk mendapatkan id 10 berikutnya dari 2 yang dikembalikan dari urutan DB. Tampaknya hanya hal yang mengganggu adalah selama server restart sering dan ini adalah masalah saya dengan celah yang lebih besar.

Jadi, ketika kita menggunakan ID SEQUENCE, id yang dimasukkan di tabel tidak akan cocok dengan nomor SEQUENCE di DB.

Jihwan
sumber
1

Setelah menggali kode sumber hibernasi dan konfigurasi Di bawah ini, buka Oracle db untuk nilai berikutnya setelah 50 penyisipan. Jadi, buat kenaikan INST_PK_SEQ Anda menjadi 50 setiap kali dipanggil.

Hibernate 5 digunakan untuk strategi di bawah ini

Periksa juga di bawah ini http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#identifiers-generators-sequence

@Id
@Column(name = "ID")
@GenericGenerator(name = "INST_PK_SEQ", 
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
        @org.hibernate.annotations.Parameter(
                name = "optimizer", value = "pooled-lo"),
        @org.hibernate.annotations.Parameter(
                name = "initial_value", value = "1"),
        @org.hibernate.annotations.Parameter(
                name = "increment_size", value = "50"),
        @org.hibernate.annotations.Parameter(
                name = SequenceStyleGenerator.SEQUENCE_PARAM, value = "INST_PK_SEQ"),
    }
)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "INST_PK_SEQ")
private Long id;
fatih tekin
sumber
3
Maaf, tapi ini adalah cara yang sangat verbose untuk mengatur sesuatu, yang dapat diekspresikan dengan mudah dengan dua parameter untuk keseluruhan Hibernate dan dengan demikian untuk semua entitas.
G. Demecki
benar tetapi ketika saya mencoba dengan cara lain tidak ada yang berhasil jika Anda berhasil dapat mengirimkan kepada saya bagaimana Anda telah mengkonfigurasi
fatih tekin
Saya telah memperbarui jawaban saya - sekarang termasuk juga contoh yang berfungsi. Meskipun komentar saya di atas sebagian salah: sayangnya, Anda tidak dapat mengatur allocationSizemaupun initialValuesecara global untuk semua entitas (kecuali hanya menggunakan satu generator, tetapi IMHO itu tidak terlalu mudah dibaca).
G. Demecki
1
Terima kasih atas penjelasannya tetapi apa yang Anda tulis di atas saya telah mencoba dan tidak berfungsi dengan hibernate 5.0.7. Versi akhir maka saya telah menggali kode sumber untuk dapat mencapai tujuan ini dan itulah implementasi yang dapat saya temukan dalam kode sumber hibernasi. Konfigurasi mungkin terlihat buruk tetapi sayangnya itu hibernate api dan saya menggunakan Implementasi EntityManager standar hibernate
fatih tekin
1

Saya juga menghadapi masalah ini di Hibernate 5:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
@SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE)
private Long titId;

Dapatkan peringatan seperti ini di bawah ini:

Ditemukan penggunaan generator id berbasis urutan [org.hibernate.id.SequenceHiLoGenerator] yang tidak digunakan lagi; gunakan org.hibernate.id.enhanced.SequenceStyleGenerator sebagai gantinya. Lihat Panduan Pemetaan Model Domain Hibernate untuk detailnya.

Kemudian ubah kode saya menjadi SequenceStyleGenerator:

@Id
@GenericGenerator(name="cmrSeq", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
        parameters = {
                @Parameter(name = "sequence_name", value = "SEQUENCE")}
)
@GeneratedValue(generator = "sequence_name")
private Long titId;

Ini memecahkan dua masalah saya:

  1. Peringatan yang tidak berlaku lagi telah diperbaiki
  2. Sekarang id dibuat sesuai urutan oracle.
Mohamed Afzal
sumber
0

Saya akan memeriksa DDL untuk urutan dalam skema. Implementasi JPA hanya bertanggung jawab atas pembuatan urutan dengan ukuran alokasi yang benar. Oleh karena itu, jika ukuran alokasinya adalah 50 maka urutan Anda harus memiliki kenaikan 50 di DDL-nya.

Kasus ini biasanya dapat terjadi dengan pembuatan urutan dengan ukuran alokasi 1 yang kemudian dikonfigurasi ke ukuran alokasi 50 (atau default) tetapi urutan DDL tidak diperbarui.

Hasan Ceylan
sumber
Anda salah paham tentang maksud saya. ALTER SEQUENCE ... INCREMENTY BY 50;tidak akan menyelesaikan apa pun, karena masalahnya masih tetap sama. Nilai urutan masih tidak mencerminkan ID entitas yang sebenarnya.
G.Demecki
Bagikan kasus uji agar kami dapat lebih memahami masalahnya di sini.
Hasan Ceylan
1
Kasus cobaan? Mengapa? Pertanyaan yang saya posting tidak terlalu rumit dan sudah dijawab. Sepertinya Anda belum mengetahui cara kerja generator HiLo. Bagaimanapun: terima kasih telah mengorbankan waktu dan tenaga Anda.
G. Demecki
1
Gregory, Sebenarnya saya tahu apa yang saya bicarakan, saya telah menulis Batoo JPA yaitu Implementasi% 100 JPA yang saat ini sedang dalam masa inkubasi dan mengalahkan Hibernate dalam hal kecepatan - 15 kali lebih cepat. Di sisi lain, saya mungkin telah salah memahami pertanyaan Anda dan tidak berpikir bahwa menggunakan Hibernate dengan urutan akan menimbulkan masalah sama sekali karena saya telah menggunakan Hibernate sejak 2003 di banyak proyek di banyak database. Yang penting Anda mendapat jawaban atas pertanyaan tersebut, maaf saya melewatkan jawaban yang ditandai sebagai benar ...
Hasan Ceylan
Maaf, saya tidak bermaksud menyinggung Anda. Sekali lagi terima kasih atas bantuan Anda, pertanyaan telah dijawab.
G. Demecki