Collection de cartes JPA d'Enums
Question
Existe-t-il un moyen dans JPA de mapper une collection d’énums au sein de la classe Entity? Ou bien 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 d'Hibernate JPA, mais je préférerais bien sûr une solution indépendante de l'implémentation.
La solution
vous pouvez utiliser Hibernate
@CollectionOfElements(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
Autres conseils
Le lien dans la réponse de Andy est un excellent point de départ pour mapper des collections de " non-Entity " dans JPA 2, mais n’est pas assez complet en matière de cartographie d’énums. Voici ce que j’ai trouvé à 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;
}
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 une erreur de chargement paresseux, comme expliqué ici .
J'utilise une légère modification de java.util.RegularEnumSet pour créer 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'enchères:
@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:
- Utilisation d'un enum sécuritaire et performant défini dans votre code (voir
java.util.EnumSet
pour une description) - L'ensemble n'est constitué que d'une colonne numérique dans la base de données
- tout est JPA simple (aucun type personnalisé spécifique au fournisseur)
- déclaration facile (et brève) de nouveaux champs du même type, par rapport aux autres solutions
Inconvénients:
- La duplication de code (
RegularEnumSet
etPersistentEnumSet
sont presque identiques)- Vous pouvez insérer le résultat de
EnumSet.noneOf(enumType)
dans votrePersistenEnumSet
, déclarerAccessType.PROPERTY
et fournir deux méthodes d'accès utilisant la réflexion pour lire et écrire le champelements
- Vous pouvez insérer le résultat de
- 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
à... interests = new PersistentEnumSet<>(InterestsEnum.class);
et supprimer le classe supplémentaire (@AttributeOverride
)
- Si votre fournisseur de persistance prend en charge les éléments incorporables sans constructeur public, vous pouvez ajouter
- Vous devez utiliser un
values()
, comme indiqué dans mon exemple, si vous avez plusieursEnumSet.getUniverse()
dans votre entité (sinon les deux seraient stockés dans la même colonne " éléments ") - L'accès de
sun.misc
avec réflexion dans le constructeur n'est pas optimal (surtout lorsque vous regardez la performance), mais les deux autres options ont aussi leurs inconvénients:- Une implémentation telle que <=> utilise une <=> classe
- 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 contenant jusqu'à 64 valeurs sont prises en charge (s'agit-il vraiment d'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 dans JPQL
- Vous pouvez utiliser des opérateurs binaires ou une colonne de masque binaire avec les fonctions appropriées, si votre base de données le prend en charge
Les collections dans JPA font référence à des relations un à plusieurs ou à 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 le faites pas!).