Je recherche les différentes façons de mapper une énumération à l'aide de JPA. Je souhaite en particulier définir la valeur entière de chaque entrée d'énumération et ne sauvegarder que la valeur entière.
@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {
public enum Right {
READ(100), WRITE(200), EDITOR (300);
private int value;
Right(int value) { this.value = value; }
public int getValue() { return value; }
};
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
// the enum to map :
private Right right;
}
Une solution simple consiste à utiliser l'annotation Enumerated avec EnumType.ORDINAL:
@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;
Mais dans ce cas, JPA mappe l'index d'énumération (0,1,2) et non la valeur que je veux (100,200,300).
Les deux solutions que j'ai trouvées ne me semblent pas simples ...
Première solution
Une solution, proposée ici , utilise @PrePersist et @PostLoad pour convertir l'énumération en un autre champ et marquer le champ enum comme transitoire:
@Basic
private int intValueForAnEnum;
@PrePersist
void populateDBFields() {
intValueForAnEnum = right.getValue();
}
@PostLoad
void populateTransientFields() {
right = Right.valueOf(intValueForAnEnum);
}
Deuxième solution
La deuxième solution proposée ici proposait un objet de conversion générique, mais semble toujours lourd et orienté hibernation (@Type ne semble pas exister en Java EE):
@Type(
type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
parameters = {
@Parameter(
name = "enumClass",
value = "Authority$Right"),
@Parameter(
name = "identifierMethod",
value = "toInt"),
@Parameter(
name = "valueOfMethod",
value = "fromInt")
}
)
Y a-t-il d'autres solutions?
J'ai plusieurs idées en tête mais je ne sais pas si elles existent dans JPA:
- utiliser les méthodes setter et getter du membre droit de la classe d'autorité lors du chargement et de l'enregistrement de l'objet Authority
- une idée équivalente serait de dire à JPA quelles sont les méthodes de Right enum pour convertir enum en int et int en enum
- Étant donné que j'utilise Spring, existe-t-il un moyen de dire à JPA d'utiliser un convertisseur spécifique (RightEditor)?
Réponses:
Pour les versions antérieures à JPA 2.1, JPA ne propose que deux façons de traiter les énumérations, par leur
name
ou par leurordinal
. Et le JPA standard ne prend pas en charge les types personnalisés. Alors:UserType
, EclipseLinkConverter
, etc.). (la deuxième solution). ~ ou ~int
valeur ~ ou ~Je vais illustrer la dernière option (il s'agit d'une implémentation de base, ajustez-la si nécessaire):
la source
C'est désormais possible avec JPA 2.1:
Plus de détails:
la source
@Converter
, maisenum
devrait être manipulé plus élégamment dès la sortie de la boîte!AttributeConverter
MAIS cite du code qui ne fait rien de tel et ne répond pas à l'OP.AttributeConverter
@Convert(converter = Converter.class) @Enumerated(EnumType.STRING)
Depuis JPA 2.1, vous pouvez utiliser AttributeConverter .
Créez une classe énumérée comme ceci:
Et créez un convertisseur comme celui-ci:
Sur l'entité dont vous avez juste besoin:
Votre chance avec
@Converter(autoApply = true)
peut varier selon le conteneur, mais testé pour fonctionner sur Wildfly 8.1.0. Si cela ne fonctionne pas, vous pouvez ajouter@Convert(converter = NodeTypeConverter.class)
la colonne de classe d'entité.la source
La meilleure approche serait de mapper un identifiant unique à chaque type d'énumération, évitant ainsi les pièges de ORDINAL et STRING. Voir ce post qui décrit 5 façons de mapper une énumération.
Tiré du lien ci-dessus:
1 et 2. Utilisation de @Enumerated
Il existe actuellement 2 façons de mapper des énumérations dans vos entités JPA à l'aide de l'annotation @Enumerated. Malheureusement, EnumType.STRING et EnumType.ORDINAL ont leurs limites.
Si vous utilisez EnumType.String, renommer l'un de vos types d'énumération entraînera la désynchronisation de votre valeur d'énumération avec les valeurs enregistrées dans la base de données. Si vous utilisez EnumType.ORDINAL, la suppression ou la réorganisation des types dans votre énumération entraînera le mappage des valeurs enregistrées dans la base de données vers les types d'énumérations incorrects.
Ces deux options sont fragiles. Si l'énumération est modifiée sans effectuer de migration de base de données, vous risquez de compromettre l'intégrité de vos données.
3. Rappels de cycle de vie
Une solution possible serait d'utiliser les annotations de rappel du cycle de vie JPA, @PrePersist et @PostLoad. Cela semble assez moche car vous aurez maintenant deux variables dans votre entité. Un mappant la valeur stockée dans la base de données et l'autre, l'énumération réelle.
4. Mappage de l'ID unique à chaque type d'énumération
La solution préférée consiste à mapper votre énumération à une valeur fixe, ou ID, définie dans l'énumération. Le mappage à une valeur fixe prédéfinie rend votre code plus robuste. Toute modification de l'ordre des types d'énumérations, ou la refactorisation des noms, n'entraînera aucun effet indésirable.
5. Utilisation de Java EE7 @Convert
Si vous utilisez JPA 2.1, vous avez la possibilité d'utiliser la nouvelle annotation @Convert. Cela nécessite la création d'une classe de convertisseur, annotée avec @Converter, à l'intérieur de laquelle vous définiriez les valeurs enregistrées dans la base de données pour chaque type d'énumération. Dans votre entité, vous annoteriez ensuite votre énumération avec @Convert.
Ma préférence: (Numéro 4)
La raison pour laquelle je préfère définir mes identifiants dans l'énumération plutôt que d'utiliser un convertisseur, c'est une bonne encapsulation. Seul le type enum doit connaître son ID, et seule l'entité doit savoir comment elle mappe l'énumération à la base de données.
Voir l'original après pour l'exemple de code.
la source
Le problème est, je pense, que JPA n'a jamais été conçu avec l'idée que nous pourrions avoir un schéma préexistant complexe déjà en place.
Je pense qu'il y a deux lacunes principales qui en résultent, spécifiques à Enum:
Aidez ma cause et votez sur JPA_SPEC-47
Ne serait-ce pas plus élégant que d'utiliser un @Converter pour résoudre le problème?
la source
Code lié éventuellement proche de Pascal
la source
Je ferais ce qui suit:
Déclarez séparément l'énumération, dans son propre fichier:
Déclarer une nouvelle entité JPA nommée Right
Vous aurez également besoin d'un convertisseur pour recevoir ces valeurs (JPA 2.1 uniquement et il y a un problème que je ne discuterai pas ici avec ces énumérations pour être directement persistant en utilisant le convertisseur, donc ce sera une route à sens unique)
L'entité Autorité:
En procédant de cette manière, vous ne pouvez pas définir directement le champ enum. Cependant, vous pouvez définir le champ Droit dans Autorité en utilisant
Et si vous avez besoin de comparer, vous pouvez utiliser:
Ce qui est plutôt cool, je pense. Ce n'est pas totalement correct, car le convertisseur n'est pas destiné à être utilisé avec les enum. En fait, la documentation dit de ne jamais l'utiliser à cette fin, vous devriez utiliser l'annotation @Enumerated à la place. Le problème est qu'il n'y a que deux types d'énumérations: ORDINAL ou STRING, mais ORDINAL est délicat et dangereux.
Cependant, si cela ne vous satisfait pas, vous pouvez faire quelque chose d'un peu plus hacky et plus simple (ou pas).
Voyons voir.
Le RightEnum:
et l'entité Autorité
Dans cette seconde idée, ce n'est pas une situation parfaite puisque nous piratons l'attribut ordinal, mais c'est un codage beaucoup plus petit.
Je pense que la spécification JPA devrait inclure le EnumType.ID où le champ de valeur enum devrait être annoté avec une sorte d'annotation @EnumId.
la source
Ma propre solution pour résoudre ce type de mappage Enum JPA est la suivante.
Étape 1 - Écrivez l'interface suivante que nous utiliserons pour toutes les énumérations que nous voulons mapper à une colonne db:
Étape 2 - Implémentez un convertisseur JPA générique personnalisé comme suit:
Cette classe convertira la valeur enum
E
en un champ de base de données de typeT
(par exempleString
) en utilisantgetDbVal()
on enumE
, et vice versa.Étape 3 - Laissez l'énumération d'origine implémenter l'interface que nous avons définie à l'étape 1:
Étape 4 - Étendez le convertisseur de l'étape 2 pour l'
Right
énumération de l'étape 3:Étape 5 - La dernière étape consiste à annoter le champ dans l'entité comme suit:
Conclusion
À mon humble avis, c'est la solution la plus propre et la plus élégante si vous avez de nombreuses énumérations à mapper et que vous souhaitez utiliser un champ particulier de l'énumération elle-même comme valeur de mappage.
Pour toutes les autres énumérations de votre projet qui nécessitent une logique de mappage similaire, il vous suffit de répéter les étapes 3 à 5, c'est-à-dire:
IDbValue
sur votre énumération;EnumDbValueConverter
avec seulement 3 lignes de code (vous pouvez également le faire dans votre entité pour éviter de créer une classe séparée);@Convert
fromjavax.persistence
package.J'espère que cela t'aides.
la source
premier cas (
EnumType.ORDINAL
)deuxième cas (
EnumType.STRING
)la source