Ada beberapa diskusi di sini tentang entitas JPA dan hashCode()
/ equals()
implementasi yang harus digunakan untuk kelas entitas JPA. Sebagian besar (jika tidak semua) dari mereka bergantung pada Hibernate, tapi saya ingin membahasnya implementasi JPA-netral (saya menggunakan EclipseLink, omong-omong).
Semua implementasi yang mungkin memiliki kelebihan dan kekurangan sendiri mengenai:
hashCode()
/equals()
Kesesuaian kontrak (kekekalan) untukList
/Set
operasi- Apakah objek yang identik (misalnya dari sesi yang berbeda, proksi dinamis dari struktur data yang malas) dapat dideteksi
- Apakah entitas berperilaku benar dalam kondisi terlepas (atau tidak bertahan)
Sejauh yang bisa saya lihat, ada tiga opsi :
- Jangan menimpa mereka; andalkan
Object.equals()
danObject.hashCode()
hashCode()
/equals()
bekerja- tidak dapat mengidentifikasi objek identik, masalah dengan proksi dinamis
- tidak ada masalah dengan entitas yang terpisah
- Timpa mereka, berdasarkan kunci utama
hashCode()
Sayaequals()
rusak- identitas yang benar (untuk semua entitas yang dikelola)
- masalah dengan entitas yang terpisah
- Mengganti mereka, berdasarkan pada Business-Id (bidang kunci non-primer; bagaimana dengan kunci asing?)
hashCode()
Sayaequals()
rusak- identitas yang benar (untuk semua entitas yang dikelola)
- tidak ada masalah dengan entitas yang terpisah
Pertanyaan saya adalah:
- Apakah saya melewatkan opsi dan / atau titik pro / kontra?
- Pilihan apa yang Anda pilih dan mengapa?
PEMBARUAN 1:
Dengan " hashCode()
/ equals()
rusak", maksud saya bahwa hashCode()
pemanggilan yang berurutan dapat mengembalikan nilai yang berbeda, yang (bila diterapkan dengan benar) tidak rusak dalam arti Object
dokumentasi API, tetapi yang menyebabkan masalah ketika mencoba untuk mengambil entitas yang diubah dari Map
,, Set
atau lainnya berbasis hash Collection
. Akibatnya, implementasi JPA (setidaknya EclipseLink) tidak akan berfungsi dengan benar dalam beberapa kasus.
PEMBARUAN 2:
Terima kasih atas jawaban Anda - kebanyakan dari mereka memiliki kualitas luar biasa.
Sayangnya, saya masih tidak yakin pendekatan mana yang akan menjadi yang terbaik untuk aplikasi kehidupan nyata, atau bagaimana menentukan pendekatan terbaik untuk aplikasi saya. Jadi, saya akan membuka pertanyaan dan berharap untuk beberapa diskusi dan / atau pendapat.
hashcode()
instance objek yang sama harus mengembalikan nilai yang sama, kecuali bidang apa pun yang digunakan dalamequals()
perubahan implementasi. Dengan kata lain, jika Anda memiliki tiga bidang di kelas Anda danequals()
metode Anda hanya menggunakan dua bidang untuk menentukan persamaan contoh, maka Anda dapat mengharapkan nilaihashcode()
pengembalian berubah jika Anda mengubah salah satu dari nilai bidang tersebut - yang masuk akal ketika Anda mempertimbangkan bahwa instance objek ini tidak lagi "sama" dengan nilai yang diwakili oleh instance lama.Jawaban:
Baca artikel yang sangat bagus tentang subjek ini: Jangan Biarkan Hibernasi Mencuri Identitas Anda .
Kesimpulan artikelnya seperti ini:
sumber
suid.js
mengambil blok ID darisuid-server-java
mana Anda bisa mendapatkan dan menggunakan sisi klien.Saya selalu menimpa sama dengan / kode hash dan menerapkannya berdasarkan id bisnis. Tampaknya solusi yang paling masuk akal bagi saya. Lihat tautan berikut .
EDIT :
Untuk menjelaskan mengapa ini bekerja untuk saya:
sumber
Kami biasanya memiliki dua ID di entitas kami:
equals()
danhashCode()
khususnya)Lihatlah:
EDIT: untuk memperjelas poin saya tentang panggilan ke
setUuid()
metode. Berikut ini adalah skenario yang khas:Ketika saya menjalankan tes saya dan melihat output log saya memperbaiki masalahnya:
Atau, seseorang dapat menyediakan konstruktor yang terpisah:
Jadi contoh saya akan terlihat seperti ini:
Saya menggunakan konstruktor default dan setter, tetapi Anda mungkin menemukan pendekatan dua konstruktor lebih cocok untuk Anda.
sumber
hashCode
/equals
metode untuk kesetaraan JVM danid
untuk kesetaraan kegigihan? Ini sama sekali tidak masuk akal bagi saya.Object
'sequals()
akan kembalifalse
dalam kasus ini. UUID berbasisequals()
kembalitrue
.Saya pribadi sudah menggunakan ketiga stategies ini di proyek yang berbeda. Dan saya harus mengatakan bahwa opsi 1 menurut saya adalah yang paling praktis dalam aplikasi kehidupan nyata. Dalam pengalaman saya, melanggar kode hashCode () / equals () mengarah ke banyak bug gila karena Anda akan setiap kali berakhir dalam situasi di mana hasil perubahan kesetaraan setelah entitas telah ditambahkan ke koleksi.
Tetapi ada opsi lebih lanjut (juga dengan pro dan kontra mereka):
a) kode hash / sederajat berdasarkan satu set berubah , tidak null , konstruktor ditugaskan , bidang
(+) ketiga kriteria dijamin
(-) nilai bidang harus tersedia untuk membuat instance baru
(-) mempersulit penanganan jika Anda harus mengubah salah satunya
b) kode hash / sama dengan berdasarkan kunci utama yang ditetapkan oleh aplikasi (dalam konstruktor) bukan JPA
(+) ketiga kriteria dijamin
(-) Anda tidak dapat memanfaatkan stategasi pembuatan ID sederhana yang andal seperti urutan DB
(-) rumit jika entitas baru dibuat di lingkungan terdistribusi (klien / server) atau server aplikasi
c) kode hash / sama dengan yang didasarkan pada UUID yang diberikan oleh konstruktor entitas
(+) ketiga kriteria dijamin
(-) overhead pembuatan UUID
(-) mungkin sedikit risiko bahwa dua kali UUID yang sama digunakan, tergantung pada algorythm yang digunakan (dapat dideteksi oleh indeks unik pada DB)
sumber
Collection
.Jika Anda ingin menggunakan
equals()/hashCode()
Set Anda, dalam arti bahwa entitas yang sama hanya dapat berada di sana satu kali, maka hanya ada satu opsi: Opsi 2. Itu karena kunci utama untuk entitas menurut definisi tidak pernah berubah (jika seseorang memang memperbarui itu, itu bukan entitas yang sama lagi)Anda harus menerimanya secara harfiah: Karena Anda
equals()/hashCode()
didasarkan pada kunci utama, Anda tidak boleh menggunakan metode ini, sampai kunci primer ditetapkan. Jadi Anda tidak harus meletakkan entitas di dalam himpunan, sampai mereka diberi kunci utama. (Ya, UUID dan konsep serupa dapat membantu menetapkan kunci primer lebih awal.)Sekarang, secara teoritis juga mungkin untuk mencapai itu dengan Opsi 3, meskipun apa yang disebut "kunci bisnis" memiliki kelemahan buruk yang dapat mereka ubah: "Yang harus Anda lakukan adalah menghapus entitas yang sudah dimasukkan dari set (). s), dan masukkan kembali. " Itu benar - tetapi itu juga berarti, bahwa dalam sistem terdistribusi, Anda harus memastikan, bahwa ini dilakukan sepenuhnya di mana pun data telah dimasukkan ke (dan Anda harus memastikan, bahwa pembaruan dilakukan , sebelum hal lain terjadi). Anda akan memerlukan mekanisme pembaruan yang canggih, terutama jika beberapa sistem jarak jauh saat ini tidak dapat dijangkau ...
Opsi 1 hanya dapat digunakan, jika semua objek di set Anda berasal dari sesi Hibernate yang sama. Dokumentasi Hibernate memperjelas hal ini di bab 13.1.3. Mempertimbangkan identitas objek :
Itu terus memperdebatkan mendukung Opsi 3:
Ini benar, jika Anda
Jika tidak, Anda bebas memilih Opsi 2.
Kemudian disebutkan perlunya stabilitas relatif:
Ini benar. Masalah praktis yang saya lihat dengan ini adalah: Jika Anda tidak dapat menjamin stabilitas absolut, bagaimana Anda dapat menjamin stabilitas "selama objek berada di Set yang sama". Saya bisa membayangkan beberapa kasus khusus (seperti menggunakan set hanya untuk percakapan dan kemudian membuangnya), tetapi saya akan mempertanyakan kepraktisan umum ini.
Versi pendek:
sumber
equals
/hashCode
.Object
sama dengan implementasi default dan kode hash karena itu tidak berfungsi setelah Andamerge
dan entitas.Anda dapat menggunakan pengenal entitas seperti yang disarankan dalam posting ini . Satu-satunya tangkapan adalah bahwa Anda perlu menggunakan
hashCode
implementasi yang selalu mengembalikan nilai yang sama, seperti ini:sumber
Meskipun menggunakan kunci bisnis (opsi 3) adalah pendekatan yang paling sering direkomendasikan ( wiki komunitas Hibernate , "Java Persistence with Hibernate" hal. 398), dan inilah yang paling sering kami gunakan, ada bug Hibernate yang memecah ini untuk diambil dengan cepat. set: HHH-3799 . Dalam hal ini, Hibernate dapat menambahkan entitas ke set sebelum bidangnya diinisialisasi. Saya tidak yakin mengapa bug ini tidak mendapat perhatian lebih, karena itu benar-benar membuat pendekatan kunci bisnis yang direkomendasikan menjadi masalah.
Saya pikir inti permasalahannya adalah bahwa equals dan kode hash harus didasarkan pada status tidak berubah (referensi Odersky et al. ), Dan entitas Hibernate dengan kunci utama yang dikelola Hibernate tidak memiliki status tetap seperti itu. Kunci utama dimodifikasi oleh Hibernate ketika objek transien menjadi persisten. Kunci bisnis juga dimodifikasi oleh Hibernate, ketika menghidrasi sebuah objek dalam proses diinisialisasi.
Yang tersisa hanya opsi 1, mewarisi implementasi java.lang.Object berdasarkan identitas objek, atau menggunakan kunci primer yang dikelola aplikasi seperti yang disarankan oleh James Brundege di "Jangan Biarkan Hibernasi Curi Identitas Anda" (sudah dirujuk oleh jawaban Stijn Geukens ) dan oleh Lance Arlaus dalam " Pembuatan Objek: Pendekatan yang Lebih Baik untuk Hibernate Integration" .
Masalah terbesar dengan opsi 1 adalah bahwa instance terpisah tidak dapat dibandingkan dengan instance persisten menggunakan .equals (). Tapi tidak apa-apa; kontrak equals dan hashCode menyerahkan kepada pengembang untuk memutuskan apa arti kesetaraan untuk setiap kelas. Jadi biarkan equals dan hashCode mewarisi dari Object. Jika Anda perlu membandingkan turunan terpisah dengan turunan persisten, Anda dapat membuat metode baru secara eksplisit untuk tujuan itu, mungkin
boolean sameEntity
atauboolean dbEquivalent
atauboolean businessEquals
.sumber
Saya setuju dengan jawaban Andrew. Kami melakukan hal yang sama dalam aplikasi kami tetapi alih-alih menyimpan UUID sebagai VARCHAR / CHAR, kami membaginya menjadi dua nilai panjang. Lihat UUID.getLeastSignificantBits () dan UUID.getMostSignificantBits ().
Satu hal lagi yang perlu dipertimbangkan, adalah bahwa panggilan ke UUID.randomUUID () cukup lambat, jadi Anda mungkin ingin melihat pembuatan UUID secara malas hanya saat dibutuhkan, seperti saat ketekunan atau panggilan untuk equals () / hashCode ()
sumber
Seperti yang sudah ditunjukkan oleh orang lain yang lebih pintar dari saya, ada banyak strategi di luar sana. Namun sepertinya sebagian besar pola desain yang diterapkan mencoba meretas jalan mereka menuju kesuksesan. Mereka membatasi akses konstruktor jika tidak menghalangi permintaan konstruktor sepenuhnya dengan konstruktor khusus dan metode pabrik. Memang selalu menyenangkan dengan API yang jelas. Tetapi jika satu-satunya alasan adalah untuk membuat equals- dan kode hash menimpa kompatibel dengan aplikasi, maka saya bertanya-tanya apakah strategi tersebut sesuai dengan KISS (Keep It Simple Stupid).
Bagi saya, saya suka menimpa sama dan kode hash dengan cara memeriksa id. Dalam metode ini, saya meminta id untuk tidak menjadi nol dan mendokumentasikan perilaku ini dengan baik. Dengan demikian akan menjadi kontrak pengembang untuk bertahan entitas baru sebelum menyimpannya di tempat lain. Aplikasi yang tidak menghormati kontrak ini akan gagal dalam hitungan menit (semoga).
Namun, peringatan: Jika entitas Anda disimpan di tabel yang berbeda dan penyedia Anda menggunakan strategi pembuatan otomatis untuk kunci utama, maka Anda akan mendapatkan duplikat kunci utama di seluruh tipe entitas. Dalam kasus seperti itu, juga membandingkan jenis waktu berjalan dengan panggilan ke Object # getClass () yang tentu saja akan membuat tidak mungkin bahwa dua jenis yang berbeda dianggap sama. Itu cocok untuk saya sebagian besar.
sumber
Object#getClass()
itu buruk karena H. proxy. MemanggilHibernate.getClass(o)
membantu, tetapi masalah kesetaraan entitas dari berbagai jenis tetap. Ada solusi menggunakan canEqual , sedikit rumit, tetapi dapat digunakan. Setuju bahwa biasanya itu tidak diperlukan. +++ Melempar dalam persamaan / hc pada null ID melanggar kontrak, tapi itu sangat pragmatis.Jelas sudah ada jawaban yang sangat informatif di sini, tetapi saya akan memberi tahu Anda apa yang kami lakukan.
Kami tidak melakukan apa pun (yaitu jangan menimpa).
Jika kita membutuhkan equals / hashcode untuk bekerja untuk koleksi, kita menggunakan UUID. Anda cukup membuat UUID di konstruktor. Kami menggunakan http://wiki.fasterxml.com/JugHome untuk UUID. UUID sedikit lebih mahal daripada CPU tetapi lebih murah dibandingkan dengan serialisasi dan akses db.
sumber
Saya selalu menggunakan opsi 1 di masa lalu karena saya menyadari diskusi ini dan berpikir lebih baik tidak melakukan apa-apa sampai saya tahu hal yang benar untuk dilakukan. Semua sistem itu masih berjalan dengan sukses.
Namun, lain kali saya dapat mencoba opsi 2 - menggunakan database yang dihasilkan Id.
Hashcode dan equals akan melempar IllegalStateException jika id tidak disetel.
Ini akan mencegah kesalahan halus yang melibatkan entitas yang belum disimpan muncul secara tidak terduga.
Apa pendapat orang tentang pendekatan ini?
sumber
Pendekatan kunci bisnis tidak cocok untuk kami. Kami menggunakan ID yang dihasilkan DB , tempId sementara sementara dan mengesampingkan equal () / hashcode () untuk menyelesaikan dilema. Semua entitas adalah keturunan Entitas. Pro:
Cons:
Lihatlah kode kami:
sumber
Silakan pertimbangkan pendekatan berikut berdasarkan pengidentifikasi tipe yang telah ditentukan dan ID.
Asumsi khusus untuk JPA:
Entitas abstrak:
Contoh entitas konkret:
Contoh uji:
Keuntungan utama di sini:
Kekurangan:
super()
Catatan:
class A
danclass B extends A
dapat bergantung pada detail konkret dari aplikasi.Menantikan komentar Anda.
sumber
Ini adalah masalah umum di setiap sistem TI yang menggunakan Java dan JPA. Titik nyeri melampaui penerapan equals () dan hashCode (), itu memengaruhi bagaimana suatu organisasi merujuk pada suatu entitas dan bagaimana kliennya merujuk pada entitas yang sama. Saya telah melihat cukup banyak rasa sakit karena tidak memiliki kunci bisnis sampai-sampai saya menulis blog sendiri untuk mengekspresikan pandangan saya.
Singkatnya: gunakan ID sekuensial pendek yang dapat dibaca manusia dengan awalan yang berarti sebagai kunci bisnis yang dihasilkan tanpa ketergantungan pada penyimpanan apa pun selain RAM. Snowflake Twitter adalah contoh yang sangat bagus.
sumber
IMO Anda memiliki 3 opsi untuk mengimplementasikan equals / hashCode
Menggunakan identitas yang dihasilkan aplikasi adalah pendekatan termudah, tetapi dilengkapi dengan beberapa kelemahan
Jika Anda dapat mengatasi kelemahan ini , gunakan saja pendekatan ini.
Untuk mengatasi masalah join, seseorang dapat menggunakan UUID sebagai kunci alami dan nilai urutan sebagai kunci primer, tetapi kemudian Anda mungkin masih mengalami masalah implementasi equals / hashCode di entitas anak komposisi yang memiliki id yang disematkan karena Anda ingin bergabung berdasarkan pada kunci utama. Menggunakan kunci alami dalam id entitas anak dan kunci utama untuk merujuk ke induk adalah kompromi yang baik.
IMO ini adalah pendekatan terbersih karena akan menghindari semua kerugian dan pada saat yang sama memberikan Anda nilai (UUID) yang dapat Anda bagikan dengan sistem eksternal tanpa mengekspos sistem internal.
Menerapkannya berdasarkan kunci bisnis jika Anda dapat berharap bahwa dari pengguna adalah ide yang bagus, tetapi juga memiliki beberapa kelemahan.
Sebagian besar waktu kunci bisnis ini akan menjadi semacam kode yang disediakan pengguna dan jarang gabungan dari beberapa atribut.
IMO Anda tidak boleh menerapkan atau bekerja dengan kunci bisnis secara eksklusif. Ini adalah add-on yang bagus yaitu pengguna dapat dengan cepat mencari dengan kunci bisnis itu, tetapi sistem tidak seharusnya bergantung padanya untuk beroperasi.
Menerapkannya berdasarkan kunci utama memiliki masalah, tapi mungkin itu bukan masalah besar
Jika Anda perlu mengekspos id ke sistem eksternal, gunakan pendekatan UUID yang saya sarankan. Jika tidak, Anda masih bisa menggunakan pendekatan UUID tetapi Anda tidak harus melakukannya. Masalah menggunakan id yang dihasilkan oleh DBMS sama dengan / kode hash berasal dari kenyataan bahwa objek mungkin telah ditambahkan ke koleksi berbasis hash sebelum menetapkan id.
Cara yang jelas untuk menyiasatinya adalah dengan tidak menambahkan objek ke koleksi berbasis hash sebelum menetapkan id. Saya mengerti bahwa ini tidak selalu mungkin karena Anda mungkin ingin deduplikasi sebelum menetapkan id. Untuk tetap dapat menggunakan koleksi berbasis hash, Anda hanya perlu membangun kembali koleksi setelah menetapkan id.
Anda dapat melakukan sesuatu seperti ini:
Saya sendiri belum menguji pendekatan yang tepat, jadi saya tidak yakin bagaimana mengubah koleksi di acara pra dan pasca-persisten bekerja tetapi idenya adalah:
Cara lain untuk menyelesaikan ini adalah dengan hanya membangun kembali semua model berbasis hash Anda setelah pembaruan / bertahan.
Pada akhirnya, terserah Anda. Saya pribadi menggunakan pendekatan berbasis urutan sebagian besar waktu dan hanya menggunakan pendekatan UUID jika saya perlu mengekspos pengidentifikasi ke sistem eksternal.
sumber
Dengan gaya baru
instanceof
dari java 14, Anda dapat menerapkannyaequals
dalam satu baris.sumber
Jika UUID adalah jawaban bagi banyak orang, mengapa kita tidak hanya menggunakan metode pabrik dari lapisan bisnis untuk membuat entitas dan menetapkan kunci utama pada waktu pembuatan?
sebagai contoh:
dengan cara ini kita akan mendapatkan kunci primer default untuk entitas dari penyedia persistence, dan fungsi kode hash () dan equals () kita bisa mengandalkan itu.
Kami juga dapat menyatakan bahwa konstruktor Mobil dilindungi dan kemudian menggunakan refleksi dalam metode bisnis kami untuk mengaksesnya. Dengan cara ini pengembang tidak akan berniat membuat Instantiate dengan yang baru, tetapi melalui metode pabrik.
Bagaimana dengan itu?
sumber
Saya mencoba menjawab pertanyaan ini sendiri dan tidak pernah benar-benar senang dengan solusi yang ditemukan sampai saya membaca posting ini dan terutama DREW satu. Saya suka cara dia malas membuat UUID dan menyimpannya secara optimal.
Tapi saya ingin menambahkan lebih banyak fleksibilitas, yaitu malas membuat UUID SAJA ketika hashCode () / equals () diakses sebelum persistensi pertama entitas dengan keunggulan masing-masing solusi:
Saya akan sangat menghargai umpan balik pada solusi campuran saya di bawah ini
sumber
Dalam praktiknya tampaknya, Opsi 2 (kunci Primer) paling sering digunakan. Kunci bisnis yang alami dan tidak dapat digerakkan jarang terjadi, membuat dan mendukung kunci sintetis terlalu berat untuk menyelesaikan situasi, yang mungkin tidak pernah terjadi. Lihatlah implementasi AbstractPersistable spring-data-jpa (satu-satunya: untuk penggunaan implementasi Hibernate
Hibernate.getClass
).Hanya sadar memanipulasi objek baru di HashSet / HashMap. Sebaliknya, Opsi 1 (tetap
Object
implementasi) rusak setelah itumerge
, itu adalah situasi yang sangat umum.Jika Anda tidak memiliki kunci bisnis dan memiliki kebutuhan NYATA untuk memanipulasi entitas baru dalam struktur hash,
hashCode
ganti ke konstan, seperti di bawah ini Vlad Mihalcea disarankan.sumber
Di bawah ini adalah solusi sederhana (dan teruji) untuk Scala.
Perhatikan bahwa solusi ini tidak sesuai dengan salah satu dari 3 kategori yang diberikan dalam pertanyaan.
Semua Entitas saya adalah subkelas dari UUIDEntity jadi saya mengikuti prinsip don't-repeat-yourself (DRY).
Jika diperlukan, pembuatan UUID dapat dibuat lebih tepat (dengan menggunakan lebih banyak angka pseudo-acak).
Kode Scala:
sumber