JPA-Kartensammlung von Enums

89

Gibt es in JPA eine Möglichkeit, eine Sammlung von Aufzählungen innerhalb der Entitätsklasse zuzuordnen? Oder besteht die einzige Lösung darin, Enum mit einer anderen Domänenklasse zu versehen und damit die Sammlung zuzuordnen?

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

Ich verwende die HPA-Implementierung von Hibernate, würde aber natürlich eine implementierungsunabhängige Lösung bevorzugen.

Gennady Shumakher
quelle

Antworten:

112

Mit Hibernate können Sie tun

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

quelle
136
Falls jemand dies jetzt liest ... @CollectionOfElements ist jetzt veraltet, verwenden Sie stattdessen: @ElementCollection
2
Sie finden ein Beispiel in der Antwort auf diese Frage: stackoverflow.com/q/3152787/363573
Stephan
1
Da Sie Hibernate erwähnt haben, dachte ich, dass diese Antwort möglicherweise herstellerspezifisch war, aber ich denke nicht, dass dies der Fall ist, es sei denn, die Verwendung von JoinTable verursacht Probleme bei anderen Implementierungen. Nach allem, was ich gesehen habe, sollte CollectionTable stattdessen verwendet werden. Das habe ich in meiner Antwort verwendet und es funktioniert für mich (obwohl ja, ich benutze gerade auch Hibernate.)
spaaarky21
Ich weiß, dass dies ein alter Thread ist, aber wir implementieren das Gleiche mit javax.persistence. Wenn wir hinzufügen: @ElementCollection (targetClass = Roles.class) @CollectionTable (name = "USER_ROLES", joinColumns = @ JoinColumn (name = "USER_ID")) @Column (name = "ROLE", nullable = false) @Enumerated ( EnumType.STRING) private Set <Roles> -Rollen; In unserer Benutzertabelle blättern die Dinge im gesamten Modellpaket aus. In unserem User-Objekt werfen sogar ein Fehler im @ Id ... @ GeneratedValue-Generator des Primärschlüssels und der erste @ OneToMany beim Erstellen doofe Fehler.
LinuxLars
Für das, was es wert ist - die Fehler, die ich sehe, sind ein Fehler - Issues.jboss.org/browse/JBIDE-16016
LinuxLars
65

Der Link in Andys Antwort ist ein guter Ausgangspunkt für die Zuordnung von Sammlungen von "Nicht-Entity" -Objekten in JPA 2, ist jedoch bei der Zuordnung von Aufzählungen nicht vollständig. Hier ist, was ich mir stattdessen ausgedacht habe.

@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
quelle
4
Leider haben einige "Administratoren" beschlossen, diese Antwort ohne Angabe von Gründen zu löschen (ungefähr gleichwertig für den Kurs hier). Als Referenz ist es datanucleus.org/products/accessplatform_3_0/jpa/orm/…
DataNucleus
2
Alles, was Sie tatsächlich brauchen, ist @ElementCollectionund Collection<InterestsEnum> interests; Der Rest ist möglicherweise nützlich, aber unnötig. Fügt beispielsweise von @Enumerated(EnumType.STRING)Menschen lesbare Zeichenfolgen in Ihre Datenbank ein.
CorayThan
2
Sie haben Recht - in diesem Beispiel können Sie sich darauf verlassen @Column, namedass das impliziert wird. Ich wollte nur klarstellen, was impliziert wird, wenn @Column weggelassen wird. Und @Enumerated wird immer empfohlen, da Ordnungszahlen eine schreckliche Sache sind. :)
spaaarky21
Ich denke, es ist erwähnenswert, dass Sie tatsächlich person_interest Tabelle brauchen
lukaszrys
Ich musste den Parameter joinColumn hinzufügen, damit es funktioniert@CollectionTable(name="person_interest", joinColumns = {@JoinColumn(name="person_id")})
Tiago
7

Dies konnte ich auf einfache Weise erreichen:

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

Eifriges Laden ist erforderlich, um ein verzögertes Laden zu vermeiden, wie hier erläutert .

Megalucio
quelle
5

Ich verwende eine geringfügige Änderung von java.util.RegularEnumSet, um ein dauerhaftes EnumSet zu erhalten:

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

Diese Klasse ist jetzt die Basis für alle meine Aufzählungssätze:

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

Und dieses Set kann ich in meiner Entität verwenden:

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

Vorteile:

  • Arbeiten mit einer in Ihrem Code festgelegten typsicheren und performanten Aufzählung ( java.util.EnumSetBeschreibung siehe )
  • Der Satz ist nur eine numerische Spalte in der Datenbank
  • Alles ist einfach JPA (keine anbieterspezifischen benutzerdefinierten Typen )
  • einfache (und kurze) Deklaration neuer Felder des gleichen Typs im Vergleich zu den anderen Lösungen

Nachteile:

  • Codeduplizierung ( RegularEnumSetund PersistentEnumSetsind fast gleich)
    • Sie können das Ergebnis von EnumSet.noneOf(enumType)in your PersistenEnumSeteinbinden, deklarieren AccessType.PROPERTYund zwei Zugriffsmethoden bereitstellen, die Reflektion zum Lesen und Schreiben des elementsFelds verwenden
  • Für jede Enum-Klasse, die in einer persistenten Menge gespeichert werden soll, wird eine zusätzliche Mengenklasse benötigt
    • Wenn Ihre Persistenz - Provider Embeddables ohne öffentlichen Konstruktor unterstützt, könnten Sie hinzufügen @Embeddablezu PersistentEnumSetund die Extraklasse fallen ( ... interests = new PersistentEnumSet<>(InterestsEnum.class);)
  • Sie müssen ein verwenden @AttributeOverride, wie in meinem Beispiel angegeben, wenn Sie mehr als eines PersistentEnumSetin Ihrer Entität haben (andernfalls würden beide in derselben Spalte "Elemente" gespeichert).
  • Der Zugriff values()mit Reflexion im Konstruktor ist nicht optimal (insbesondere wenn man die Leistung betrachtet), aber die beiden anderen Optionen haben auch ihre Nachteile:
    • Eine Implementierung wie EnumSet.getUniverse()verwendet eine sun.miscKlasse
    • Wenn Sie das Wertearray als Parameter angeben, besteht das Risiko, dass die angegebenen Werte nicht die richtigen sind
  • Es werden nur Aufzählungen mit bis zu 64 Werten unterstützt (ist das wirklich ein Nachteil?)
    • Sie können stattdessen BigInteger verwenden
  • Es ist nicht einfach, das Elementfeld in einer Kriterienabfrage oder in JPQL zu verwenden
    • Sie können Binäroperatoren oder eine Bitmaskenspalte mit den entsprechenden Funktionen verwenden, wenn Ihre Datenbank dies unterstützt
Tobias Liefke
quelle
2

Eine kurze Lösung wäre die folgende:

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

Die lange Antwort lautet, dass JPA mit diesen Anmerkungen eine Tabelle erstellt, die die Liste von InterestsEnum enthält, die auf die Hauptklassen-ID (in diesem Fall Person.class) verweist.

@ElementCollections geben an, wo JPA Informationen zur Aufzählung finden kann

@CollectionTable erstellt die Tabelle, die die Beziehung von Person zu InterestsEnum enthält

@Enumerated (EnumType.STRING) weist JPA an, die Aufzählung als Zeichenfolge beizubehalten. Dies könnte EnumType.ORDINAL sein

mizerablebr
quelle
0

Sammlungen in JPA beziehen sich auf Eins-zu-Viele- oder Viele-zu-Viele-Beziehungen und können nur andere Entitäten enthalten. Entschuldigung, aber Sie müssten diese Aufzählungen in eine Entität einschließen. Wenn Sie darüber nachdenken, benötigen Sie ein ID-Feld und einen Fremdschlüssel, um diese Informationen trotzdem zu speichern. Es sei denn, Sie tun etwas Verrücktes wie das Speichern einer durch Kommas getrennten Liste in einem String (tun Sie dies nicht!).

Cletus
quelle
8
Dies gilt nur für JPA 1.0. In JPA 2.0 können Sie die Annotation @ElementCollection wie oben gezeigt verwenden.
Rustyx