Saya telah bekerja dengan JPA (implementasi Hibernate) untuk beberapa waktu sekarang dan setiap kali saya perlu membuat entitas saya menemukan diri saya berjuang dengan masalah sebagai AccessType, properti tidak berubah, sama dengan / kode hash, ....
Jadi saya memutuskan untuk mencoba dan menemukan praktik terbaik umum untuk setiap masalah dan menuliskannya untuk penggunaan pribadi.
Namun saya tidak keberatan bagi siapa pun untuk mengomentarinya atau mengatakan di mana saya salah.
Kelas Entitas
mengimplementasikan Serializable
Alasan: Spesifikasi mengatakan Anda harus melakukannya, tetapi beberapa penyedia JPA tidak menegakkan ini. Hibernate sebagai penyedia JPA tidak menegakkan ini, tetapi bisa gagal di suatu tempat jauh di dalam perutnya dengan ClassCastException, jika Serializable belum dilaksanakan.
Konstruktor
membuat konstruktor dengan semua bidang entitas yang diperlukan
Alasan: Seorang konstruktor harus selalu membiarkan instance dibuat dalam keadaan waras.
selain konstruktor ini: memiliki konstruktor default pribadi paket
Alasan: Konstruktor default diperlukan agar Hibernate menginisialisasi entitas; pribadi diperbolehkan tetapi paket privat (atau publik) diperlukan untuk pembuatan proxy runtime dan pengambilan data yang efisien tanpa instrumentasi bytecode.
Bidang / Properti
Gunakan akses lapangan secara umum dan akses properti saat dibutuhkan
Alasan: ini mungkin masalah yang paling bisa diperdebatkan karena tidak ada argumen yang jelas dan meyakinkan untuk satu atau yang lain (akses properti vs akses lapangan); Namun, akses bidang tampaknya menjadi favorit umum karena kode yang lebih jelas, enkapsulasi yang lebih baik dan tidak perlu membuat setter untuk bidang yang tidak dapat diubah
Abaikan setter untuk bidang yang tidak dapat diubah (tidak diperlukan untuk bidang tipe akses)
- properti mungkin bersifat pribadi
Alasan: Saya pernah mendengar bahwa dilindungi lebih baik untuk kinerja (Hibernate) tetapi yang dapat saya temukan di web adalah: Hibernate dapat mengakses metode publik, privat, dan terlindungi, serta bidang publik, swasta dan terlindungi secara langsung . Pilihannya terserah Anda dan Anda dapat mencocokkannya agar sesuai dengan desain aplikasi Anda.
Kode yang sama / hash
- Jangan pernah gunakan id yang dihasilkan jika id ini hanya disetel saat mempertahankan entitas
- Menurut preferensi: gunakan nilai yang tidak dapat diubah untuk membentuk Kunci Bisnis yang unik dan gunakan ini untuk menguji kesetaraan
- jika Kunci Bisnis unik tidak tersedia gunakan UUID non-transien yang dibuat ketika entitas diinisialisasi; Lihat artikel hebat ini untuk informasi lebih lanjut.
- tidak pernah merujuk ke entitas terkait (ManyToOne); jika entitas ini (seperti entitas induk) perlu menjadi bagian dari Kunci Bisnis, maka bandingkan ID saja. Memanggil getId () pada proxy tidak akan memicu pemuatan entitas, selama Anda menggunakan tipe akses properti .
Entitas Contoh
@Entity
@Table(name = "ROOM")
public class Room implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "room_id")
private Integer id;
@Column(name = "number")
private String number; //immutable
@Column(name = "capacity")
private Integer capacity;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable
Room() {
// default constructor
}
public Room(Building building, String number) {
// constructor with required field
notNull(building, "Method called with null parameter (application)");
notNull(number, "Method called with null parameter (name)");
this.building = building;
this.number = number;
}
@Override
public boolean equals(final Object otherObj) {
if ((otherObj == null) || !(otherObj instanceof Room)) {
return false;
}
// a room can be uniquely identified by it's number and the building it belongs to; normally I would use a UUID in any case but this is just to illustrate the usage of getId()
final Room other = (Room) otherObj;
return new EqualsBuilder().append(getNumber(), other.getNumber())
.append(getBuilding().getId(), other.getBuilding().getId())
.isEquals();
//this assumes that Building.id is annotated with @Access(value = AccessType.PROPERTY)
}
public Building getBuilding() {
return building;
}
public Integer getId() {
return id;
}
public String getNumber() {
return number;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getNumber()).append(getBuilding().getId()).toHashCode();
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
Saran lain untuk ditambahkan ke daftar ini lebih dari diterima ...
MEMPERBARUI
Sejak membaca artikel ini saya telah mengadaptasi cara saya menerapkan eq / hC:
- jika kunci bisnis sederhana yang kekal tersedia: gunakan itu
- dalam semua kasus lain: gunakan uuid
final
(menilai oleh setingan Anda setter, saya kira Anda juga).notNull
?Jawaban:
Saya akan mencoba menjawab beberapa poin utama: ini dari pengalaman Hibernate / kegigihan yang panjang termasuk beberapa aplikasi utama.
Kelas Entitas: implement Serializable?
Kunci perlu mengimplementasikan Serializable. Hal-hal yang akan masuk dalam HttpSession, atau dikirim melalui kawat oleh RPC / Java EE, perlu mengimplementasikan Serializable. Hal-hal lain: tidak begitu banyak. Luangkan waktu Anda untuk hal-hal penting.
Konstruktor: membuat konstruktor dengan semua bidang yang diperlukan dari entitas?
Konstruktor untuk logika aplikasi, harus memiliki hanya beberapa bidang "kunci asing" atau "jenis / jenis" kritis yang akan selalu diketahui saat membuat entitas. Sisanya harus ditetapkan dengan memanggil metode setter - itulah gunanya.
Hindari menempatkan terlalu banyak bidang ke dalam konstruktor. Konstruktor harus nyaman, dan memberikan kewarasan dasar pada objek. Nama, Jenis dan / atau Orang Tua semuanya biasanya berguna.
OTOH jika aturan aplikasi (hari ini) mengharuskan Pelanggan memiliki Alamat, serahkan kepada setter. Itu adalah contoh dari "aturan lemah". Mungkin minggu depan, Anda ingin membuat objek Pelanggan sebelum pergi ke layar Enter Details? Jangan tersandung, tinggalkan kemungkinan untuk data yang tidak diketahui, tidak lengkap atau "sebagian dimasukkan".
Konstruktor: juga, paket konstruktor default pribadi?
Ya, tetapi gunakan 'protected' daripada paket pribadi. Hal-hal subclass adalah rasa sakit yang nyata ketika internal yang diperlukan tidak terlihat.
Bidang / Properti
Gunakan akses bidang 'properti' untuk Hibernate, dan dari luar instance. Dalam instance, gunakan bidang secara langsung. Alasan: memungkinkan refleksi standar, metode paling sederhana & paling dasar untuk Hibernate, berfungsi.
Adapun bidang 'tidak berubah' ke aplikasi - Hibernate masih harus dapat memuat ini. Anda dapat mencoba membuat metode ini 'pribadi', dan / atau memberikan anotasi pada mereka, untuk mencegah kode aplikasi membuat akses yang tidak diinginkan.
Catatan: saat menulis fungsi equals (), gunakan getter untuk nilai pada instance 'other'! Jika tidak, Anda akan menekan bidang yang tidak diinisialisasi / kosong pada instance proxy.
Terlindungi lebih baik untuk kinerja (Hibernate)?
Tidak sepertinya.
Sama dengan / HashCode?
Ini relevan untuk bekerja dengan entitas, sebelum mereka diselamatkan - yang merupakan masalah pelik. Hashing / membandingkan nilai-nilai yang tidak berubah? Di sebagian besar aplikasi bisnis, tidak ada.
Seorang pelanggan dapat mengubah alamat, mengubah nama bisnis mereka, dll. - tidak umum, tetapi itu terjadi. Koreksi juga perlu dilakukan, ketika data tidak dimasukkan dengan benar.
Beberapa hal yang biasanya tetap tidak berubah, adalah Pola Asuh dan mungkin Tipe / Jenis - biasanya pengguna membuat ulang catatan, alih-alih mengubahnya. Tetapi ini tidak secara unik mengidentifikasi entitas!
Jadi, panjang dan pendek, data "tidak berubah" yang diklaim tidak benar. Bidang Kunci Utama / ID dibuat untuk tujuan yang tepat, untuk memberikan stabilitas & kekekalan yang dijamin.
Anda perlu merencanakan & mempertimbangkan kebutuhan Anda untuk perbandingan & hashing & fase kerja pemrosesan permintaan ketika A) bekerja dengan "data yang diubah / terikat" dari UI jika Anda membandingkan / hash pada "bidang yang jarang diubah", atau B) bekerja dengan " data yang belum disimpan ", jika Anda membandingkan / hash pada ID.
Equals / HashCode - jika Kunci Bisnis unik tidak tersedia, gunakan UUID non-transien yang dibuat ketika entitas diinisialisasi
Ya, ini adalah strategi yang baik saat dibutuhkan. Ketahuilah bahwa UUID tidak bebas, dari segi kinerja - dan pengelompokan mempersulit.
Equals / HashCode - tidak pernah merujuk ke entitas terkait
"Jika entitas terkait (seperti entitas induk) perlu menjadi bagian dari Kunci Bisnis, lalu tambahkan bidang yang tidak dapat dimasukkan, tidak dapat diupdate untuk menyimpan id induk (dengan nama yang sama dengan ManyCone JoinColumn) dan gunakan id ini dalam pemeriksaan kesetaraan "
Kedengarannya seperti saran yang bagus.
Semoga ini membantu!
sumber
The JPA 2.0 Keterangan menyatakan bahwa:
Spesifikasi tidak mengandung persyaratan tentang penerapan metode hashCode sama dan untuk entitas, hanya untuk kelas kunci utama dan kunci peta sejauh yang saya tahu.
sumber
Tambahan 2 sen saya untuk jawaban di sini adalah:
Dengan mengacu pada akses Field atau Properti (jauh dari pertimbangan kinerja) keduanya secara sah diakses melalui getter dan setter, dengan demikian, logika model saya dapat mengatur / mendapatkannya dengan cara yang sama. Perbedaannya muncul ketika penyedia runtime persistence (Hibernate, EclipseLink atau yang lain) perlu bertahan / menetapkan beberapa catatan dalam Tabel A yang memiliki kunci asing yang merujuk ke beberapa kolom pada Tabel B. Dalam kasus tipe akses Properti, persistence sistem runtime menggunakan metode penyetel kode saya untuk menetapkan nilai baru pada sel di kolom B. Dalam kasus tipe akses Field, sistem runtime persistensi mengatur sel dalam kolom Tabel B secara langsung. Perbedaan ini tidak penting dalam konteks hubungan satu arah, namun itu HARUS menggunakan metode setter kode saya sendiri (tipe akses properti) untuk hubungan dua arah asalkan metode setter dirancang dengan baik untuk memperhitungkan konsistensi. Konsistensi adalah masalah penting untuk hubungan dua arah yang merujuk pada hal initautan untuk contoh sederhana untuk setter yang dirancang dengan baik.
Dengan mengacu pada Equals / hashCode: Tidak mungkin untuk menggunakan metode Equals / hashCode yang dihasilkan otomatis Eclipse untuk entitas yang berpartisipasi dalam hubungan dua arah, jika tidak mereka akan memiliki referensi melingkar yang menghasilkan Pengecualian stackoverflow. Setelah Anda mencoba hubungan dua arah (katakanlah OneToOne) dan hasil-otomatis sama dengan () atau kode hash () atau bahkan toString () Anda akan terjebak dalam pengecualian stackoverflow ini.
sumber
Antarmuka entitas
Implementasi dasar untuk semua Entitas, menyederhanakan implementasi Equals / Hashcode:
Entitas Entitas Kamar:
Saya tidak melihat titik membandingkan kesetaraan entitas berdasarkan bidang bisnis dalam setiap kasus Entitas JPA. Itu mungkin lebih merupakan kasus jika entitas JPA ini dianggap sebagai Domain-Driven ValueObjects, bukan Domain-Driven Entities (yang contoh kode ini untuk).
sumber