Zbiór map JPA wyliczeń

93

Czy w JPA istnieje sposób mapowania kolekcji wyliczeń w klasie Entity? A może jedynym rozwiązaniem jest opakowanie Enum z inną klasą domeny i użycie jej do zmapowania kolekcji?

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

Używam implementacji Hibernate JPA, ale oczywiście wolałbym rozwiązanie agnostyczne.

Giennadij Szumakher
źródło

Odpowiedzi:

112

używając Hibernacji, możesz to zrobić

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

źródło
141
Na wypadek, gdyby ktoś to teraz czytał ... @CollectionOfElements jest teraz przestarzałe, zamiast tego użyj: @ElementCollection
2
Próbkę można znaleźć w odpowiedzi na to pytanie: stackoverflow.com/q/3152787/363573
Stephan
1
Odkąd wspomniałeś o Hibernate, pomyślałem, że ta odpowiedź może być specyficzna dla dostawcy, ale nie sądzę, że tak jest, chyba że użycie JoinTable powoduje problemy w innych implementacjach. Z tego, co widziałem, uważam, że zamiast tego należy użyć CollectionTable. To jest to, czego użyłem w mojej odpowiedzi i to działa dla mnie (chociaż tak, teraz używam również Hibernate'a).
spaaarky21
Wiem, że to stary wątek, ale wdrażamy ten sam rodzaj rzeczy za pomocą javax.persistence. Kiedy dodamy: @ElementCollection (targetClass = Roles.class) @CollectionTable (name = "USER_ROLES", joinColumns = @ JoinColumn (name = "USER_ID")) @Column (name = "ROLE", nullable = false) @Enumerated ( EnumType.STRING) private Ustaw role <Roles>; do naszej tabeli użytkowników, rzeczy odpadają w całym pakiecie modeli. W naszym obiekcie User nawet błąd w generatorze @Id ... @ GeneratedValue klucza podstawowego i pierwszy @OneToMany generują głupie błędy podczas kompilacji.
LinuxLars,
Na co warto - błędy, które widzę to błąd - Issues.jboss.org/browse/JBIDE-16016
LinuxLars
65

Łącze w odpowiedzi Andy'ego jest świetnym punktem wyjścia do mapowania kolekcji obiektów „nie-Encji” w JPA 2, ale nie jest kompletne, jeśli chodzi o mapowanie wyliczeń. Oto, co wymyśliłem zamiast tego.

@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
źródło
4
Niestety, jakiś „admin” zdecydował się usunąć tę odpowiedź bez podania powodu (około par dla kursu tutaj). Dla porównania jest to datanucleus.org/products/accessplatform_3_0/jpa/orm/…
DataNucleus
2
Wszystko, czego potrzebujesz, to @ElementCollectioni Collection<InterestsEnum> interests; reszta jest potencjalnie przydatna, ale niepotrzebna. Na przykład @Enumerated(EnumType.STRING)umieszcza w bazie danych ciągi czytelne dla człowieka.
CorayThan
2
Masz rację - w tym przykładzie, można polegać na @Column„s namejest dorozumiany. Chciałem tylko wyjaśnić, co jest implikowane, gdy pominięto @Column. A @Enumerated jest zawsze zalecane, ponieważ liczba porządkowa jest okropną rzeczą do domyślnej. :)
spaaarky21
Myślę, że warto wspomnieć, że faktycznie potrzebujesz tabeli
person_interest
Musiałem dodać parametr joinColumn, aby to działało@CollectionTable(name="person_interest", joinColumns = {@JoinColumn(name="person_id")})
Tiago
8

Udało mi się to osiągnąć w ten prosty sposób:

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

Zachłanne ładowanie jest wymagane, aby uniknąć leniwego błędu inicjalizacji ładowania, jak wyjaśniono tutaj .

megalucio
źródło
5

Używam niewielkiej modyfikacji java.util.RegularEnumSet, aby mieć trwały zestaw EnumSet:

@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
  // ...
}

Ta klasa jest teraz podstawą dla wszystkich moich zestawów wyliczeń:

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

I ten zestaw, którego mogę używać w mojej jednostce:

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

Zalety:

  • Praca z bezpiecznym i wydajnym zestawem wyliczeń w kodzie (zobacz java.util.EnumSetopis)
  • Zestaw to tylko jedna kolumna liczbowa w bazie danych
  • wszystko jest zwykłym JPA (brak niestandardowych typów specyficznych dla dostawcy )
  • łatwe (i krótkie) deklarowanie nowych pól tego samego typu w porównaniu z innymi rozwiązaniami

Wady:

  • Powielanie kodu ( RegularEnumSeti PersistentEnumSetprawie takie same)
    • Możesz zawinąć wynik EnumSet.noneOf(enumType)w PersistenEnumSet, zadeklarować AccessType.PROPERTYi zapewnić dwie metody dostępu, które używają odbicia do odczytu i zapisu elementspola
  • Dodatkowa klasa zestawu jest wymagana dla każdej klasy wyliczenia, która powinna być przechowywana w zestawie trwałym
    • Jeśli dostawca wytrwałość obsługuje embeddables bez konstruktora publicznego, można dodać @Embeddabledo PersistentEnumSeti upuść dodatkową klasę ( ... interests = new PersistentEnumSet<>(InterestsEnum.class);)
  • Musisz użyć znaku @AttributeOverride, jak podano w moim przykładzie, jeśli masz więcej niż jeden PersistentEnumSetw swojej encji (w przeciwnym razie oba będą przechowywane w tej samej kolumnie „elementy”)
  • Dostęp values()z odbiciem w konstruktorze nie jest optymalny (zwłaszcza patrząc na wykonanie), ale dwie inne opcje mają również swoje wady:
    • Implementacja taka jak EnumSet.getUniverse()wykorzystuje sun.miscklasę
    • Podanie tablicy wartości jako parametru stwarza ryzyko, że podane wartości nie są poprawne
  • Obsługiwane są tylko wyliczenia zawierające maksymalnie 64 wartości (czy to naprawdę wada?)
    • Zamiast tego możesz użyć BigInteger
  • Nie jest łatwo używać pola elementów w zapytaniu kryterialnym lub JPQL
    • Możesz użyć operatorów binarnych lub kolumny maski bitowej z odpowiednimi funkcjami, jeśli twoja baza danych to obsługuje
Tobias Liefke
źródło
4

tl; dr Krótkie rozwiązanie byłoby następujące:

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

Długą odpowiedzią jest to, że dzięki tym adnotacjom JPA utworzy jedną tabelę, która będzie zawierała listę InterestsEnum wskazującą na główny identyfikator klasy (w tym przypadku Person.class).

@ElementCollections określ, gdzie JPA może znaleźć informacje o Enum

@CollectionTable utwórz tabelę zawierającą relację od Person do InterestsEnum

@Enumerated (EnumType.STRING) mówi JPA, aby zachować Enum jako ciąg, może to być EnumType.ORDINAL

mizerablebr
źródło
W tym przypadku nie mogę zmienić tej kolekcji, ponieważ jest ona zapisywana jako zestaw PersistenceSet, który jest niezmienny.
Nicolazz92
Mój błąd. Możemy zmienić ten zestaw za pomocą setera.
Nicolazz92
0

Kolekcje w JPA odnoszą się do relacji jeden do wielu lub wiele do wielu i mogą zawierać tylko inne jednostki. Przepraszamy, ale musisz opakować te wyliczenia w encję. Jeśli się nad tym zastanowić, i tak potrzebowałbyś jakiegoś pola ID i klucza obcego do przechowywania tych informacji. Chyba że zrobisz coś szalonego, na przykład przechowywanie listy oddzielonej przecinkami w łańcuchu (nie rób tego!).

cletus
źródło
8
Dotyczy to tylko JPA 1.0. W JPA 2.0 możesz użyć adnotacji @ElementCollection, jak pokazano powyżej.
rustyx