Collection de cartes JPA d'énumérations

93

Existe-t-il un moyen dans JPA de mapper une collection d'énumérations dans la classe Entity? Ou la seule solution consiste à encapsuler Enum avec une autre classe de domaine et à l'utiliser pour mapper la collection?

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

J'utilise la mise en œuvre Hibernate JPA, mais je préférerais bien sûr une solution indépendante de la mise en œuvre.

Gennady Shumakher
la source

Réponses:

112

en utilisant Hibernate, vous pouvez faire

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

la source
141
Au cas où quelqu'un lirait ceci maintenant ... @CollectionOfElements est maintenant obsolète, utilisez à la place: @ElementCollection
2
Vous pouvez trouver un exemple dans la réponse à cette question: stackoverflow.com/q/3152787/363573
Stephan
1
Depuis que vous avez mentionné Hibernate, je pensais que cette réponse pouvait être spécifique au fournisseur, mais je ne pense pas que ce soit le cas à moins que l'utilisation de JoinTable ne cause des problèmes dans d'autres implémentations. D'après ce que j'ai vu, je pense que CollectionTable devrait être utilisé à la place. C'est ce que j'ai utilisé dans ma réponse et cela fonctionne pour moi (même si oui, j'utilise également Hibernate en ce moment.)
spaaarky21
Je sais que c'est un vieux fil, mais nous implémentons le même genre de chose en utilisant javax.persistence. Lorsque nous ajoutons: @ElementCollection (targetClass = Roles.class) @CollectionTable (name = "USER_ROLES", joinColumns = @ JoinColumn (name = "USER_ID")) @Column (name = "ROLE", nullable = false) @Enumerated ( EnumType.STRING) private Set <Roles> rôles; dans notre table User, les choses se retrouvent dans l'ensemble du modèle. Dans notre objet User, même une erreur sur le générateur @Id ... @ GeneratedValue de la clé primaire et le premier @OneToMany lancent des erreurs loufoques lors de la construction.
LinuxLars
Pour ce que ça vaut - les erreurs que je vois sont un bogue - issues.jboss.org/browse/JBIDE-16016
LinuxLars
65

Le lien dans la réponse d'Andy est un excellent point de départ pour mapper des collections d'objets "non-Entity" dans JPA 2, mais n'est pas tout à fait complet lorsqu'il s'agit de mapper des énumérations. Voici ce que j'ai proposé à la place.

@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
la source
4
Malheureusement, un "administrateur" a décidé de supprimer cette réponse sans donner de raison (à peu près par pour le cours ici). Pour la référence c'est datanucleus.org/products/accessplatform_3_0/jpa/orm/…
DataNucleus
2
Tout ce dont vous avez réellement besoin est @ElementCollectionet Collection<InterestsEnum> interests; Le reste est potentiellement utile mais inutile. Par exemple, @Enumerated(EnumType.STRING)met des chaînes lisibles par l'homme dans votre base de données.
CorayJour
2
Vous avez raison - dans cet exemple, vous pouvez vous fier au @Column'sname sont implicites. Je voulais juste clarifier ce qui est impliqué lorsque @Column est omis. Et @Enumerated est toujours recommandé car ordinal est une chose horrible par défaut. :)
spaaarky21
Je pense qu'il vaut la peine de mentionner que vous avez réellement besoin d'une table person_interest
person_interest
J'ai dû ajouter le paramètre joinColumn pour que cela fonctionne@CollectionTable(name="person_interest", joinColumns = {@JoinColumn(name="person_id")})
Tiago
8

J'ai pu accomplir cela de cette manière simple:

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

Un chargement hâtif est nécessaire afin d'éviter une erreur d'initialisation du chargement paresseux comme expliqué ici .

megalucio
la source
5

J'utilise une légère modification de java.util.RegularEnumSet pour avoir un EnumSet persistant:

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

Cette classe est maintenant la base de tous mes ensembles d'énumérations:

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

Et cet ensemble que je peux utiliser dans mon entité:

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

Avantages:

  • Travailler avec une énumération de type sûre et performante dans votre code (voir java.util.EnumSet pour une description)
  • L'ensemble est juste une colonne numérique dans la base de données
  • tout est JPA simple (aucun type personnalisé spécifique au fournisseur )
  • déclaration simple (et courte) de nouveaux champs du même type, par rapport aux autres solutions

Désavantages:

  • Duplication de code (RegularEnumSet et PersistentEnumSetsont presque les mêmes)
    • Vous pouvez envelopper le résultat de EnumSet.noneOf(enumType)dans votre PersistenEnumSet, déclarer AccessType.PROPERTYet fournir deux méthodes d'accès qui utilisent la réflexion pour lire et écrire le elementschamp
  • Une classe d'ensemble supplémentaire est nécessaire pour chaque classe d'énumération qui doit être stockée dans un ensemble persistant
    • Si votre fournisseur de persistance prend en charge intégrables sans constructeur public, vous pouvez ajouter @Embeddableà PersistentEnumSetdéposer la classe supplémentaire ( ... interests = new PersistentEnumSet<>(InterestsEnum.class);)
  • Vous devez utiliser un @AttributeOverride, comme indiqué dans mon exemple, si vous en avez plus d'unPersistentEnumSet dans votre entité (sinon les deux seraient stockés dans la même colonne "éléments")
  • L'accès à values()avec réflexion dans le constructeur n'est pas optimal (surtout lorsqu'on regarde les performances), mais les deux autres options ont aussi leurs inconvénients:
    • Une implémentation comme EnumSet.getUniverse()utilise une sun.miscclasse
    • Fournir le tableau de valeurs en tant que paramètre présente le risque que les valeurs données ne soient pas les bonnes
  • Seules les énumérations avec jusqu'à 64 valeurs sont prises en charge (est-ce vraiment un inconvénient?)
    • Vous pouvez utiliser BigInteger à la place
  • Il n'est pas facile d'utiliser le champ des éléments dans une requête de critères ou JPQL
    • Vous pouvez utiliser des opérateurs binaires ou une colonne de masque de bits avec les fonctions appropriées, si votre base de données prend en charge cela
Tobias Liefke
la source
4

tl; dr Une solution courte serait la suivante:

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

La réponse longue est qu'avec ces annotations, JPA créera une table qui contiendra la liste d'InterestsEnum pointant vers l'identificateur de classe principal (Person.class dans ce cas).

@ElementCollections spécifie où JPA peut trouver des informations sur Enum

@CollectionTable crée la table qui contient la relation de Person à IntérêtsEnum

@Enumerated (EnumType.STRING) indique à JPA de conserver Enum en tant que chaîne, pourrait être EnumType.ORDINAL

mizerablebr
la source
Dans ce cas, je ne peux pas modifier cette collection car elle est enregistrée en tant que PersistenceSet qui est immuable.
Nicolazz92
Mon erreur. Nous pouvons changer cet ensemble avec un setter.
Nicolazz92
0

Les collections dans JPA font référence à des relations un-à-plusieurs ou plusieurs-à-plusieurs et elles ne peuvent contenir que d'autres entités. Désolé, mais vous devez intégrer ces énumérations dans une entité. Si vous y réfléchissez, vous aurez besoin d'une sorte de champ d'identification et de clé étrangère pour stocker ces informations de toute façon. C'est à moins que vous ne fassiez quelque chose de fou comme stocker une liste séparée par des virgules dans une chaîne (ne faites pas cela!).

cletus
la source
8
Ceci n'est valable que pour JPA 1.0. Dans JPA 2.0, vous pouvez utiliser l'annotation @ElementCollection comme indiqué ci-dessus.
rustyx