Saya mencari berbagai cara untuk memetakan enum menggunakan JPA. Saya terutama ingin mengatur nilai integer dari setiap entri enum dan hanya menyimpan nilai integer.
@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {
public enum Right {
READ(100), WRITE(200), EDITOR (300);
private int value;
Right(int value) { this.value = value; }
public int getValue() { return value; }
};
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
// the enum to map :
private Right right;
}
Solusi sederhana adalah dengan menggunakan anotasi Enumerated dengan EnumType.ORDINAL:
@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;
Tetapi dalam hal ini JPA memetakan indeks enum (0,1,2) dan bukan nilai yang saya inginkan (100,200,300).
Dua solusi yang saya temukan sepertinya tidak sederhana ...
Solusi Pertama
Solusi, diusulkan di sini , menggunakan @PrePersist dan @PostLoad untuk mengonversi enum ke bidang lain dan menandai bidang enum sebagai transien:
@Basic
private int intValueForAnEnum;
@PrePersist
void populateDBFields() {
intValueForAnEnum = right.getValue();
}
@PostLoad
void populateTransientFields() {
right = Right.valueOf(intValueForAnEnum);
}
Solusi Kedua
Solusi kedua yang diusulkan di sini mengusulkan objek konversi generik, tetapi masih tampak berat dan berorientasi hibernasi (@Type sepertinya tidak ada di Java EE):
@Type(
type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
parameters = {
@Parameter(
name = "enumClass",
value = "Authority$Right"),
@Parameter(
name = "identifierMethod",
value = "toInt"),
@Parameter(
name = "valueOfMethod",
value = "fromInt")
}
)
Apakah ada solusi lain?
Saya memiliki beberapa ide tetapi saya tidak tahu apakah mereka ada di JPA:
- menggunakan metode setter dan pengambil dari anggota kanan dari Kelas Otoritas saat memuat dan menyimpan objek Otoritas
- ide yang setara adalah untuk memberi tahu JPA apa metode Right enum untuk mengonversi enum ke int dan int ke enum
- Karena saya menggunakan Spring, apakah ada cara untuk memberitahu JPA untuk menggunakan konverter tertentu (RightEditor)?
Jawaban:
Untuk versi yang lebih awal dari JPA 2.1, JPA hanya menyediakan dua cara untuk menangani enum, dengan mereka
name
atau oleh merekaordinal
. Dan JPA standar tidak mendukung tipe khusus. Begitu:UserType
, EclipseLinkConverter
, dll). (solusi kedua). ~ atau ~int
nilai ~ atau ~Saya akan menggambarkan opsi terbaru (ini adalah implementasi dasar, atur sesuai kebutuhan):
sumber
Ini sekarang dimungkinkan dengan JPA 2.1:
Keterangan lebih lanjut:
sumber
@Converter
, tetapienum
harus ditangani dengan lebih elegan di luar kotak!AttributeConverter
TETAPI mengutip beberapa kode yang tidak melakukan hal semacam itu dan tidak menjawab OP.AttributeConverter
@Convert(converter = Converter.class) @Enumerated(EnumType.STRING)
Dari JPA 2.1 Anda dapat menggunakan AttributeConverter .
Buat kelas yang disebutkan seperti ini:
Dan buat konverter seperti ini:
Pada entitas yang Anda butuhkan:
Keberuntungan Anda
@Converter(autoApply = true)
mungkin berbeda di setiap wadah tetapi diuji untuk bekerja pada Wildfly 8.1.0. Jika tidak berhasil, Anda dapat menambahkan@Convert(converter = NodeTypeConverter.class)
pada kolom kelas entitas.sumber
Pendekatan terbaik adalah memetakan ID unik untuk setiap jenis enum, sehingga menghindari perangkap ORDINAL dan STRING. Lihat posting ini yang menguraikan 5 cara Anda dapat memetakan enum.
Diambil dari tautan di atas:
1 & 2. Menggunakan @Enumerated
Saat ini ada 2 cara Anda dapat memetakan enum dalam entitas JPA Anda menggunakan penjelasan @Enumerated. Sayangnya baik EnumType.STRING dan EnumType.ORDINAL memiliki keterbatasan.
Jika Anda menggunakan EnumType.String kemudian mengubah nama salah satu tipe enum Anda akan menyebabkan nilai enum Anda tidak sinkron dengan nilai yang disimpan dalam database. Jika Anda menggunakan EnumType.ORDINAL maka menghapus atau menata ulang jenis-jenis dalam enum Anda akan menyebabkan nilai yang disimpan dalam database dipetakan ke jenis enum yang salah.
Kedua opsi ini rapuh. Jika enum dimodifikasi tanpa melakukan migrasi basis data, Anda dapat merusak integritas data Anda.
3. Callback Daur Hidup
Sebuah solusi yang mungkin akan menggunakan anotasi panggilan balik siklus hidup JPA, @PrePersist dan @PostLoad. Ini terasa sangat jelek karena sekarang Anda akan memiliki dua variabel di entitas Anda. Satu memetakan nilai yang disimpan dalam database, dan yang lainnya, enum yang sebenarnya.
4. Memetakan ID unik untuk setiap jenis enum
Solusi yang disukai adalah memetakan enum Anda ke nilai tetap, atau ID, yang ditentukan dalam enum. Memetakan ke nilai yang telah ditentukan dan tetap membuat kode Anda lebih kuat. Setiap modifikasi pada urutan jenis enums, atau refactoring nama, tidak akan menimbulkan efek buruk.
5. Menggunakan Java EE7 @Convert
Jika Anda menggunakan JPA 2.1, Anda memiliki opsi untuk menggunakan anotasi @Convert yang baru. Ini membutuhkan pembuatan kelas konverter, dijelaskan dengan @Converter, di mana Anda akan menentukan nilai apa yang disimpan ke dalam database untuk setiap jenis enum. Di dalam entitas Anda, Anda kemudian akan membubuhi keterangan enum Anda dengan @Convert.
Preferensi saya: (Nomor 4)
Alasan mengapa saya lebih suka mendefinisikan ID saya di dalam enum sebagai lawan menggunakan konverter, adalah enkapsulasi yang baik. Hanya tipe enum yang harus tahu ID-nya, dan hanya entitas yang harus tahu tentang cara memetakan enum ke database.
Lihat posting asli untuk contoh kode.
sumber
Masalahnya adalah, saya pikir, bahwa JPA tidak pernah dibentuk dengan gagasan dalam pikiran bahwa kita dapat memiliki Skema yang sudah ada sebelumnya yang sudah ada.
Saya pikir ada dua kelemahan utama yang dihasilkan dari ini, khusus untuk Enum:
Bantu perjuangan saya dan pilih JPA_SPEC-47
Bukankah ini lebih elegan daripada menggunakan @Converter untuk menyelesaikan masalah?
sumber
Kemungkinan kode terkait Pascal terkait
sumber
Saya akan melakukan hal berikut:
Nyatakan secara terpisah enum, dalam file miliknya sendiri:
Deklarasikan entitas JPA baru bernama Kanan
Anda juga akan memerlukan konverter untuk menerima nilai-nilai ini (hanya JPA 2.1 dan ada masalah yang tidak akan saya diskusikan di sini dengan enum ini untuk secara langsung tetap menggunakan konverter, sehingga hanya akan menjadi jalan satu arah saja)
Entitas Otoritas:
Dengan melakukan cara ini, Anda tidak dapat mengatur langsung ke bidang enum. Namun, Anda dapat mengatur bidang Kanan di Otoritas menggunakan
Dan jika Anda perlu membandingkan, Anda dapat menggunakan:
Yang cukup keren, saya pikir. Itu tidak sepenuhnya benar, karena konverter itu tidak dimaksudkan untuk digunakan dengan enum. Sebenarnya, dokumentasi mengatakan untuk tidak pernah menggunakannya untuk tujuan ini, Anda harus menggunakan anotasi @Enumerated sebagai gantinya. Masalahnya adalah bahwa hanya ada dua jenis enum: ORDINAL atau STRING, tetapi ORDINAL itu rumit dan tidak aman.
Namun, jika itu tidak memuaskan Anda, Anda dapat melakukan sesuatu yang sedikit lebih rumit dan sederhana (atau tidak).
Ayo lihat.
The RightEnum:
dan entitas Otoritas
Dalam ide kedua ini, ini bukan situasi yang sempurna karena kita meretas atribut ordinal, tetapi itu adalah pengkodean yang jauh lebih kecil.
Saya berpikir bahwa spesifikasi JPA harus menyertakan EnumType.ID di mana bidang nilai enum harus dijelaskan dengan beberapa jenis penjelasan @EnumId.
sumber
Solusi saya sendiri untuk menyelesaikan pemetaan Enum JPA semacam ini adalah sebagai berikut.
Langkah 1 - Tulis antarmuka berikut yang akan kita gunakan untuk semua enum yang ingin kita petakan ke kolom db:
Langkah 2 - Menerapkan konverter JPA khusus generik sebagai berikut:
Kelas ini akan mengonversi nilai enum
E
ke bidang tipe databaseT
(misalnyaString
) dengan menggunakangetDbVal()
on enumE
, dan sebaliknya.Langkah 3 - Biarkan enum asli mengimplementasikan antarmuka yang kami tentukan di langkah 1:
Langkah 4 - Perpanjang konverter langkah 2 untuk
Right
enum langkah 3:Langkah 5 - Langkah terakhir adalah untuk membubuhi keterangan bidang dalam entitas sebagai berikut:
Kesimpulan
IMHO ini adalah solusi terbersih dan paling elegan jika Anda memiliki banyak enum untuk dipetakan dan Anda ingin menggunakan bidang enum itu sendiri sebagai nilai pemetaan.
Untuk semua enum lain dalam proyek Anda yang memerlukan logika pemetaan yang serupa, Anda hanya perlu mengulangi langkah 3 hingga 5, yaitu:
IDbValue
pada enum Anda;EnumDbValueConverter
dengan hanya 3 baris kode (Anda juga dapat melakukan ini dalam entitas Anda untuk menghindari membuat kelas yang terpisah);@Convert
darijavax.persistence
paket.Semoga ini membantu.
sumber
kasus pertama (
EnumType.ORDINAL
)kasus kedua (
EnumType.STRING
)sumber