web-dev-qa-db-fra.com

Collection de cartes JPA d'Enums

JPA existe-t-il un moyen de mapper une collection d'énums au sein de la classe Entity? Ou la seule solution consiste à envelopper 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 l'implémentation Hibernate JPA, mais je préférerais bien sûr une solution sans implémentation.

74
Gennady Shumakher

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;
98
ant

Le lien indiqué par Andy est un excellent point de départ pour mapper des collections d’objets "non-Entity" dans JPA 2, mais n’est pas assez complet en matière de mappage. Voici ce que je suis venu à 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;
}
56
spaaarky21

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

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

Un chargement rapide est nécessaire pour éviter les erreurs d’initialisation du chargement paresseux, comme expliqué ici .

4
megalucio

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 pour tous mes ensembles enum:

@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 un enum sécuritaire et performant défini 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 clair JPA (pas de fournisseur types personnalisés )
  • déclaration facile (et brève) de nouveaux champs du même type, par rapport aux autres solutions

Désavantages:

  • La duplication de code (RegularEnumSet et PersistentEnumSet sont presque les mêmes)
    • Vous pouvez insérer le résultat de EnumSet.noneOf(enumType) dans votre PersistenEnumSet, déclarer AccessType.PROPERTY et fournir deux méthodes d'accès utilisant la réflexion pour lire et écrire le champ elements.
  • Une classe de jeu supplémentaire est nécessaire pour chaque classe enum devant être stockée dans un jeu persistant
    • Si votre fournisseur de persistance prend en charge les éléments incorporables sans constructeur public, vous pouvez ajouter @Embeddable à PersistentEnumSet et supprimer la classe Extra (... interests = new PersistentEnumSet<>(InterestsEnum.class);).
  • Vous devez utiliser un @AttributeOverride, comme indiqué dans mon exemple, si vous avez plus d'une PersistentEnumSet dans votre entité (sinon les deux seraient stockés dans la même colonne "éléments")
  • L’accès de values() avec réflexion dans le constructeur n’est pas optimal (surtout lorsqu’on regarde la performance), mais les deux autres options ont aussi leurs inconvénients:
    • Une implémentation telle que EnumSet.getUniverse() utilise une classe Sun.misc
    • Fournir le tableau de valeurs en tant que paramètre présente le risque que les valeurs données ne soient pas correctes
  • Seules les énumérations avec un maximum de 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ère 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 le prend en charge.
4
Tobias Liefke

Les collections dans JPA font référence à des relations un à plusieurs ou plusieurs à plusieurs et ne peuvent contenir que d'autres entités. Désolé, mais vous devez envelopper ces enums dans une entité. Si vous y réfléchissez, vous aurez de toute façon besoin d’un champ d’identification et d’une clé étrangère pour stocker ces informations. 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 ceci!).

0
cletus