Bagaimana seharusnya sama dan kode hash diterapkan saat menggunakan JPA dan Hibernate

103

Bagaimana seharusnya model class's equals dan hashcode diimplementasikan dalam Hibernate? Apa saja jebakan yang umum? Apakah implementasi default cukup baik untuk kebanyakan kasus? Apakah ada gunanya menggunakan kunci bisnis?

Bagi saya tampaknya cukup sulit untuk membuatnya bekerja dengan benar dalam setiap situasi, ketika pengambilan lambat, pembuatan id, proxy, dll diperhitungkan.

egaga
sumber
Lihat juga stackoverflow.com/a/39827962/548473 (implementasi pegas-data-jpa)
Grigory Kislin

Jawaban:

74

Hibernate memiliki penjelasan yang bagus dan panjang tentang kapan / bagaimana menimpa equals()/ hashCode()dalam dokumentasi

Intinya adalah Anda hanya perlu mengkhawatirkannya jika entitas Anda akan menjadi bagian dari a Setatau jika Anda akan melepaskan / melampirkan instance-nya. Yang terakhir tidak begitu umum. Yang pertama biasanya paling baik ditangani melalui:

  1. Mendasarkan equals()/ hashCode()pada kunci bisnis - misalnya kombinasi unik dari atribut yang tidak akan berubah selama objek (atau, setidaknya, sesi) seumur hidup.
  2. Jika hal di atas tidak mungkin, berdasarkan equals()/ hashCode()pada kunci primer JIKA itu disetel dan identitas objek / System.identityHashCode()sebaliknya. Bagian penting di sini adalah Anda perlu memuat ulang Set Anda setelah entitas baru ditambahkan ke dalamnya dan dipertahankan; jika tidak, Anda mungkin akan mendapatkan perilaku aneh (yang pada akhirnya menyebabkan kesalahan dan / atau kerusakan data) karena entitas Anda mungkin dialokasikan ke keranjang yang tidak cocok dengan saat ini hashCode().
ChssPly76
sumber
1
Saat Anda mengatakan "reload" @ ChssPly76 yang Anda maksud adalah melakukan refresh()? Bagaimana entitas Anda, yang mematuhi Setkontrak berakhir di keranjang yang salah (dengan asumsi Anda memiliki implementasi kode hash yang cukup baik).
non sequitor
4
Segarkan koleksi atau muat ulang seluruh entitas (pemilik), ya. Sejauh keranjang yang salah: a) Anda menambahkan entitas baru untuk disetel, id-nya belum disetel, jadi Anda menggunakan identityHashCode yang menempatkan entitas Anda di keranjang # 1. b) entitas Anda (dalam set) dipertahankan, sekarang memiliki id dan karenanya Anda menggunakan hashCode () berdasarkan id itu. Ini berbeda dari yang di atas dan akan menempatkan entitas Anda di keranjang # 2. Sekarang, dengan asumsi Anda memegang referensi ke entitas ini di tempat lain, coba telepon Set.contains(entity)dan Anda akan kembali false. Hal yang sama berlaku untuk get () / put () / etc ...
ChssPly76
Masuk akal tetapi tidak pernah menggunakan identitasHashCode sendiri meskipun saya melihatnya digunakan dalam sumber Hibernate seperti di ResultTransformers mereka
non sequitor
1
Saat menggunakan Hibernate, Anda juga bisa mengalami masalah ini , yang masih belum saya temukan solusinya.
Giovanni Botta
@ ChssPly76 Karena aturan bisnis yang menentukan apakah dua objek sama, saya perlu mendasarkan metode equals / hashcode saya pada properti yang dapat berubah dalam masa pakai objek. Apakah itu masalah besar? Jika demikian, bagaimana cara menyiasatinya?
ubiquibacon
39

Saya tidak berpikir bahwa jawaban yang diterima itu akurat.

Untuk menjawab pertanyaan awal:

Apakah implementasi default cukup baik untuk kebanyakan kasus?

Jawabannya adalah ya, dalam banyak kasus memang demikian.

Anda hanya perlu mengganti equals()dan hashcode()jika entitas akan digunakan dalam Set(yang sangat umum) DAN entitas akan terlepas dari, dan kemudian dilampirkan kembali ke, sesi hibernasi (yang merupakan penggunaan hibernate yang tidak umum).

Jawaban yang diterima menunjukkan bahwa metode perlu diganti jika salah satu kondisinya benar.

Phil
sumber
Ini sejalan dengan pengamatan saya, saatnya mencari tahu mengapa .
Vlastimil Ovčáčík
"Anda hanya perlu mengganti equals () dan hashcode () jika entitas akan digunakan dalam Set" sudah cukup lengkap jika beberapa kolom mengidentifikasi objek, sehingga Anda tidak ingin bergantung pada Object.equals () untuk mengidentifikasi benda.
davidxxx
17

Penerapan equals/ terbaik hashCodeadalah ketika Anda menggunakan kunci bisnis yang unik .

Kunci bisnis harus konsisten di semua transisi status entitas (sementara, dilampirkan, dilepas, dihapus), itulah mengapa Anda tidak dapat mengandalkan id untuk kesetaraan.

Opsi lainnya adalah beralih menggunakan pengenal UUID , yang ditetapkan oleh logika aplikasi. Dengan cara ini, Anda dapat menggunakan UUID untuk equals/ hashCodekarena id ditetapkan sebelum entitas dihapus.

Anda bahkan dapat menggunakan pengenal entitas untuk equalsdan hashCode, tetapi itu mengharuskan Anda untuk selalu mengembalikan nilai yang sama hashCodesehingga Anda memastikan bahwa nilai kode hash entitas konsisten di semua transisi status entitas. Lihat posting ini untuk lebih lanjut tentang topik ini .

Vlad Mihalcea
sumber
1 untuk pendekatan uuid. Masukkan itu ke dalam BaseEntitydan jangan pernah berpikir lagi tentang masalah itu. Dibutuhkan sedikit ruang di sisi db tetapi harga itu Anda lebih baik membayar untuk kenyamanan :)
Martin Frey
12

Ketika sebuah entitas dimuat melalui pemuatan lambat, itu bukan instance dari tipe dasar, tetapi merupakan subtipe yang dibuat secara dinamis yang dibuat oleh javassist, jadi pemeriksaan pada tipe kelas yang sama akan gagal, jadi jangan gunakan:

if (getClass() != that.getClass()) return false;

sebagai gantinya gunakan:

if (!(otherObject instanceof Unit)) return false;

yang juga merupakan praktik yang baik, seperti yang dijelaskan di Implementing Equals in Java Practices .

karena alasan yang sama, mengakses bidang secara langsung, mungkin tidak berfungsi dan mengembalikan null, bukan nilai yang mendasarinya, jadi jangan gunakan perbandingan pada properti, tetapi gunakan getter, karena mereka mungkin memicu untuk memuat nilai yang mendasarinya.

stivlo
sumber
1
Ini berfungsi jika Anda membandingkan objek kelas beton, yang tidak berfungsi dalam situasi saya. Saya membandingkan objek kelas super, dalam hal ini kode ini berfungsi untuk saya: obj1.getClass (). IsInstance (obj2)
Tad
6

Ya, itu sulit. Dalam proyek saya sama dan hashCode keduanya bergantung pada id objek. Masalah dari solusi ini adalah bahwa keduanya tidak berfungsi jika objek belum dipertahankan, karena id dihasilkan oleh database. Dalam kasus saya, itu dapat ditoleransi karena di hampir semua kasus, objek langsung disimpan. Selain itu, ini berfungsi dengan baik dan mudah diterapkan.

Carlos
sumber
Apa yang saya pikir kami lakukan adalah menggunakan identitas objek dalam kasus di mana id belum dibuat
Kathy Van Stone
2
Masalahnya di sini adalah jika Anda mempertahankan objek tersebut, kode hash Anda berubah. Itu bisa berdampak buruk besar jika objek sudah menjadi bagian dari struktur data berbasis hash. Jadi, jika Anda akhirnya menggunakan identitas objek, Anda sebaiknya terus menggunakan obj id sampai objek benar-benar dibebaskan (atau hapus objek dari struktur berbasis hash, bertahan, lalu tambahkan kembali). Secara pribadi, saya pikir akan lebih baik untuk tidak menggunakan id, dan mendasarkan hash pada properti objek yang tidak dapat diubah.
Kevin Day
1

Dalam dokumentasi Hibernate 5.2 dikatakan bahwa Anda mungkin tidak ingin mengimplementasikan kode hash dan yang sama sama sekali - tergantung pada situasi Anda.

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode

Umumnya, dua objek yang dimuat dari sesi yang sama akan sama jika keduanya sama dalam database (tanpa menerapkan hashCode dan sama dengan).

Ini menjadi rumit jika Anda menggunakan dua sesi atau lebih. Dalam hal ini, persamaan dua objek bergantung pada implementasi metode-sama Anda.

Lebih lanjut, Anda akan mendapat masalah jika metode-sama Anda membandingkan ID yang hanya dibuat saat mempertahankan objek untuk pertama kalinya. Mereka mungkin belum ada di sana saat yang sama dipanggil.

Nina
sumber
0

Ada artikel yang sangat bagus di sini: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

Mengutip baris penting dari artikel:

Kami merekomendasikan penerapan equals () dan hashCode () menggunakan persamaan kunci Bisnis. Kesamaan kunci bisnis berarti bahwa metode equals () hanya membandingkan properti yang membentuk kunci bisnis, kunci yang akan mengidentifikasi contoh kita di dunia nyata (kunci kandidat alami):

Secara sederhana

public class Cat {

...
public boolean equals(Object other) {
    //Basic test / class cast
    return this.catId==other.catId;
}

public int hashCode() {
    int result;

    return 3*this.catId; //any primenumber 
}

}
Ravi Shekhar
sumber
0

Jika Anda kebetulan menimpa equals, pastikan Anda memenuhi kontraknya: -

  • SIMETRI
  • REFLEKTIF
  • TRANSITIF
  • KONSISTEN
  • NON NULL

Dan mengesampingkan hashCode, karena kontraknya bergantung pada equalsimplementasi.

Joshua Bloch (perancang kerangka Koleksi) sangat menyarankan aturan ini untuk diikuti.

  • item 9: Selalu timpa kode hash saat Anda mengganti sama

Ada efek serius yang tidak diinginkan jika Anda tidak mengikuti kontrak ini. Misalnya List#contains(Object o)mungkin mengembalikan booleannilai yang salah karena kontrak umum tidak terpenuhi.

Awan Biru
sumber