Koleksi peta JPA Enums

93

Apakah ada cara di JPA untuk memetakan kumpulan Enum dalam kelas Entitas? Atau satu-satunya solusi adalah membungkus Enum dengan kelas domain lain dan menggunakannya untuk memetakan koleksi?

@Entity
public class Person {
    public enum InterestsEnum {Books, Sport, etc...  }
    //@???
    Collection<InterestsEnum> interests;
}

Saya menggunakan implementasi Hibernate JPA, tapi tentu saja lebih suka implementasi solusi agnostik.

Gennady Shumakher
sumber

Jawaban:

112

menggunakan Hibernate dapat Anda lakukan

@CollectionOfElements(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;

sumber
141
Jika ada yang membaca ini sekarang ... @CollectionOfElements sekarang sudah usang, sebagai gantinya gunakan: @ElementCollection
2
Anda dapat menemukan contoh dalam menjawab pertanyaan ini: stackoverflow.com/q/3152787/363573
Stephan
1
Karena Anda menyebutkan Hibernate, saya pikir jawaban ini mungkin khusus vendor tetapi saya rasa tidak demikian kecuali jika menggunakan JoinTable menyebabkan masalah dalam implementasi lain. Dari apa yang saya lihat, saya percaya CollectionTable harus digunakan sebagai gantinya. Itulah yang saya gunakan dalam jawaban saya dan berfungsi untuk saya (meskipun ya, saya juga menggunakan Hibernate sekarang.)
spaaarky21
Saya tahu ini adalah utas lama, tetapi kami menerapkan hal yang sama menggunakan javax.persistence. Saat kita menambahkan: @ElementCollection (targetClass = Roles.class) @CollectionTable (name = "USER_ROLES", joinColumns = @ JoinColumn (name = "USER_ID")) @Column (name = "ROLE", nullable = false) @Enumerated ( EnumType.STRING) private Set <Roles> role; ke tabel Pengguna kami, hal-hal hilang di seluruh paket model. Di objek User kita, bahkan error pada generator @Id ... @ GeneratedValue kunci Primer dan @OneToMany pertama memunculkan error konyol pada build.
LinuxLars
Untuk apa nilainya - kesalahan yang saya lihat adalah bug - issues.jboss.org/browse/JBIDE-16016
LinuxLars
65

Tautan dalam jawaban Andy adalah titik awal yang bagus untuk memetakan koleksi objek "non-Entitas" di JPA 2, tetapi tidak cukup lengkap dalam hal enum pemetaan. Inilah yang saya temukan sebagai gantinya.

@Entity
public class Person {
    @ElementCollection(targetClass=InterestsEnum.class)
    @Enumerated(EnumType.STRING) // Possibly optional (I'm not sure) but defaults to ORDINAL.
    @CollectionTable(name="person_interest")
    @Column(name="interest") // Column name in person_interest
    Collection<InterestsEnum> interests;
}
spaaarky21
sumber
4
Sayangnya beberapa "admin" memutuskan untuk menghapus jawaban itu tanpa memberikan alasan (tentang par untuk kursus di sini). Untuk referensi, ini adalah datanucleus.org/products/accessplatform_3_0/jpa/orm/…
DataNucleus
2
Yang Anda butuhkan hanyalah @ElementCollectiondan Collection<InterestsEnum> interests; Sisanya berpotensi berguna tetapi tidak perlu. Misalnya @Enumerated(EnumType.STRING)menempatkan string yang dapat dibaca manusia di database Anda.
CorayThan
2
Anda benar - dalam contoh ini, Anda bisa mengandalkan @Column's nameyang tersirat. Saya hanya ingin mengklarifikasi apa yang tersirat ketika @Column dihilangkan. Dan @Enumerated selalu direkomendasikan karena ordinal adalah hal yang buruk untuk dijadikan default. :)
spaaarky21
Saya pikir perlu disebutkan bahwa Anda benar-benar membutuhkan tabel
person_interest
Saya harus menambahkan parameter joinColumn agar berfungsi@CollectionTable(name="person_interest", joinColumns = {@JoinColumn(name="person_id")})
Tiago
8

Saya dapat melakukannya dengan cara sederhana ini:

@ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;

Eager loading diperlukan untuk menghindari kesalahan inisialisasi pemuatan lambat seperti yang dijelaskan di sini .

megalucio
sumber
5

Saya menggunakan sedikit modifikasi java.util.RegularEnumSet untuk memiliki EnumSet yang persisten:

@MappedSuperclass
@Access(AccessType.FIELD)
public class PersistentEnumSet<E extends Enum<E>> 
    extends AbstractSet<E> {
  private long elements;

  @Transient
  private final Class<E> elementType;

  @Transient
  private final E[] universe;

  public PersistentEnumSet(final Class<E> elementType) {
    this.elementType = elementType;
    try {
      this.universe = (E[]) elementType.getMethod("values").invoke(null);
    } catch (final ReflectiveOperationException e) {
      throw new IllegalArgumentException("Not an enum type: " + elementType, e);
    }
    if (this.universe.length > 64) {
      throw new IllegalArgumentException("More than 64 enum elements are not allowed");
    }
  }

  // Copy everything else from java.util.RegularEnumSet
  // ...
}

Kelas ini sekarang menjadi basis untuk semua set enum saya:

@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
  public InterestsSet() {
    super(InterestsEnum.class);
  }
}

Dan set itu dapat saya gunakan di entitas saya:

@Entity
public class MyEntity {
  // ...
  @Embedded
  @AttributeOverride(name="elements", column=@Column(name="interests"))
  private InterestsSet interests = new InterestsSet();
}

Keuntungan:

  • Bekerja dengan jenis enum aman dan berkinerja yang ditetapkan dalam kode Anda (lihat java.util.EnumSetuntuk deskripsi)
  • Set hanya satu kolom numerik dalam database
  • semuanya JPA biasa (tidak ada jenis kustom khusus penyedia )
  • deklarasi mudah (dan singkat) bidang baru dari jenis yang sama, dibandingkan dengan solusi lain

Kekurangan:

  • Duplikasi kode ( RegularEnumSetdan PersistentEnumSethampir sama)
    • Anda dapat membungkus hasil EnumSet.noneOf(enumType)di PersistenEnumSet, mendeklarasikan AccessType.PROPERTYdan memberikan dua metode akses yang menggunakan refleksi untuk membaca dan menulis elementsbidang
  • Kelas set tambahan diperlukan untuk setiap kelas enum yang harus disimpan dalam set persisten
    • Jika penyedia kegigihan Anda mendukung embeddables tanpa konstruktor publik, Anda bisa menambahkan @Embeddableuntuk PersistentEnumSetdan drop kelas tambahan ( ... interests = new PersistentEnumSet<>(InterestsEnum.class);)
  • Anda harus menggunakan @AttributeOverride, seperti yang diberikan dalam contoh saya, jika Anda memiliki lebih dari satu PersistentEnumSetentitas Anda (jika tidak, keduanya akan disimpan ke kolom "elemen" yang sama)
  • Akses values()with refleksi di konstruktor tidak optimal (terutama saat melihat performanya), tetapi dua opsi lainnya juga memiliki kekurangan:
    • Implementasi seperti EnumSet.getUniverse()memanfaatkan sun.misckelas
    • Memberikan nilai array sebagai parameter memiliki risiko bahwa nilai yang diberikan bukan yang benar
  • Hanya enum dengan hingga 64 nilai yang didukung (apakah itu benar-benar kekurangan?)
    • Anda bisa menggunakan BigInteger sebagai gantinya
  • Tidak mudah menggunakan bidang elemen dalam kueri kriteria atau JPQL
    • Anda dapat menggunakan operator biner atau kolom bitmask dengan fungsi yang sesuai, jika database Anda mendukungnya
Tobias Liefke
sumber
4

tl; dr Solusi singkatnya adalah sebagai berikut:

@ElementCollection(targetClass = InterestsEnum.class)
@CollectionTable
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;

Jawaban panjangnya adalah dengan penjelasan ini JPA akan membuat satu tabel yang akan menampung daftar InterestsEnum yang menunjuk ke pengenal kelas utama (Person.class dalam hal ini).

@ElementCollections menentukan di mana JPA dapat menemukan informasi tentang Enum

@CollectionTable membuat tabel yang menyimpan hubungan dari Orang ke InterestsEnum

@Enumerated (EnumType.STRING) beri tahu JPA untuk mempertahankan Enum sebagai String, bisa jadi EnumType.ORDINAL

mizerablebr.dll
sumber
Dalam hal ini, saya tidak dapat mengubah koleksi ini karena disimpan sebagai PersistenceSet yang tidak dapat diubah.
Nicolazz92
Kesalahanku. Kita bisa mengubah set ini dengan setter.
Nicolazz92
0

Koleksi di JPA mengacu pada hubungan satu-ke-banyak atau banyak-ke-banyak dan mereka hanya dapat berisi entitas lain. Maaf, tapi Anda perlu menggabungkan enum tersebut dalam sebuah entitas. Jika Anda memikirkannya, Anda memerlukan semacam bidang ID dan kunci asing untuk menyimpan informasi ini. Itu kecuali Anda melakukan sesuatu yang gila seperti menyimpan daftar yang dipisahkan koma dalam String (jangan lakukan ini!).

cletus
sumber
8
Ini hanya berlaku untuk JPA 1.0. Di JPA 2.0 Anda dapat menggunakan anotasi @ElementCollection seperti yang ditunjukkan di atas.
rustyx