Carte enum dans JPA avec des valeurs fixes?

Je cherche les différentes façons de mapper un enum en utilisant JPA. Je veux surtout définir la valeur entière de chaque entrée enum et sauvegarder uniquement 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 cartographie l’indice enum (0,1,2) et non la valeur que je veux (100 200 300).

Les deux solutions que j’ai trouvées ne semblent pas simples …

Première solution

Une solution proposée ici utilise @PrePersist et @PostLoad pour convertir l’énum 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 seconde solution proposée ici proposait un object 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’object Authority
  • une idée équivalente serait de dire à JPA quelles sont les méthodes de Right enum pour convertir enum en int et int enum
  • Étant donné que j’utilise Spring, existe-t-il un moyen de demander à JPA d’utiliser un convertisseur spécifique (RightEditor)?

Pour les versions antérieures à JPA 2.1, JPA ne propose que deux manières de traiter les énumérations, par leur name ou par leur ordinal . Et le JPA standard ne prend pas en charge les types personnalisés. Alors:

  • Si vous souhaitez effectuer des conversions de type personnalisé, vous devrez utiliser une extension de fournisseur (avec Hibernate UserType , EclipseLink Converter , etc.). (la deuxième solution). ~ ou ~
  • Vous devrez utiliser les astuces @PrePersist et @PostLoad (la première solution). ~ ou ~
  • Annoter getter et setter en prenant et en renvoyant la valeur int ~ ou ~
  • Utilisez un atsortingbut d’entier au niveau de l’entité et effectuez une traduction dans les getters et les setters.

Je vais illustrer la dernière option (il s’agit d’une implémentation de base, ajustez-la au besoin):

 @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; } public static Right parse(int id) { Right right = null; // Default for (Right item : Right.values()) { if (item.getValue()==id) { right = item; break; } } return right; } }; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "AUTHORITY_ID") private Long id; @Column(name = "RIGHT_ID") private int rightId; public Right getRight () { return Right.parse(this.rightId); } public void setRight(Right right) { this.rightId = right.getValue(); } } 

Ceci est maintenant possible avec JPA 2.1:

 @Column(name = "RIGHT") @Enumerated(EnumType.STRING) private Right right; 

Plus de détails:

La meilleure approche serait de mapper un identifiant unique à chaque type d’énumération, évitant ainsi les pièges d’ORDINAL et de STRING. Voir ce post qui décrit 5 façons de mapper un enum.

Tiré du lien ci-dessus:

1 & 2. Utiliser @Enumerated

Il existe actuellement deux manières de mapper les énumérations au sein de vos entités JPA à l’aide de l’annotation @Enumerated. Malheureusement, EnumType.STRING et EnumType.ORDINAL ont tous deux leurs limites.

Si vous utilisez EnumType.Ssortingng, en renommant l’un de vos types enum, votre valeur enum ne sera plus synchronisée avec les valeurs enregistrées dans la firebase database. Si vous utilisez EnumType.ORDINAL, alors la suppression ou la réorganisation des types dans votre enum entraînera le mappage des valeurs enregistrées dans la firebase database sur les types de énumérations incorrects.

Ces deux options sont fragiles. Si l’énumération est modifiée sans effectuer une migration de la firebase database, vous pourriez compromettre l’intégrité de vos données.

3. Rappel de cycle de vie

Une solution possible consisterait à 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é. L’un mappant la valeur stockée dans la firebase database et l’autre, l’énumération réelle.

4. Mapper un identifiant unique à chaque type d’énumération

La solution préférée consiste à mapper votre enum sur une valeur fixe, ou ID, définie dans l’énumération. Le mappage à une valeur prédéfinie fixe rend votre code plus robuste. Toute modification de l’ordre des types de énumérations, ou la refactorisation des noms, ne causera 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 conversion, annotée avec @Converter, à l’intérieur de laquelle vous définirez les valeurs enregistrées dans la firebase database pour chaque type d’énumération. Dans votre entité, vous annotez alors votre compte avec @Convert.

Ma préférence: (Numéro 4)

La raison pour laquelle je préfère définir mes identifiants dans l’enum comme opposés à l’utilisation d’un convertisseur, c’est une bonne encapsulation. Seul le type enum doit connaître son identifiant et seule l’entité doit savoir comment il mappe l’énumération à la firebase database.

Voir le message original pour l’exemple de code.

Je pense que le problème est que JPA n’a jamais été saisi de l’idée que nous pourrions avoir un schéma préexistant complexe déjà en place.

Je pense qu’il en résulte deux lacunes principales, propres à Enum:

  1. La limitation de l’utilisation de name () et ordinal (). Pourquoi ne pas simplement marquer un getter avec @Id, comme nous le faisons avec @Entity?
  2. Enum a généralement une représentation dans la firebase database pour permettre l’association avec toutes sortes de métadonnées, y compris un nom propre, un nom descriptif, peut-être quelque chose avec la localisation, etc. Nous avons besoin d’une facilité d’utilisation combinée avec la flexibilité d’une entité.

Aide ma cause et vote sur JPA_SPEC-47

Est-ce que ce ne serait pas plus élégant que d’utiliser un @Converter pour résoudre le problème?

 // Note: this code won't work!! // it is just a sample of how I *would* want it to work! @Enumerated public enum Language { ENGLISH_US("en-US"), ENGLISH_BRITISH("en-BR"), FRENCH("fr"), FRENCH_CANADIAN("fr-CA"); @ID private Ssortingng code; @Column(name="DESCRIPTION") private Ssortingng description; Language(Ssortingng code) { this.code = code; } public Ssortingng getCode() { return code; } public Ssortingng getDescription() { return description; } } 

À partir de JPA 2.1, vous pouvez utiliser AtsortingbuteConverter .

Créez une classe énumérée comme suit:

 public enum NodeType { ROOT("root-node"), BRANCH("branch-node"), LEAF("leaf-node"); private final Ssortingng code; private NodeType(Ssortingng code) { this.code = code; } public Ssortingng getCode() { return code; } } 

Et créez un convertisseur comme celui-ci:

 import javax.persistence.AtsortingbuteConverter; import javax.persistence.Converter; @Converter(autoApply = true) public class NodeTypeConverter implements AtsortingbuteConverter { @Override public Ssortingng convertToDatabaseColumn(NodeType nodeType) { return nodeType.getCode(); } @Override public NodeType convertToEntityAtsortingbute(Ssortingng dbData) { for (NodeType nodeType : values()) { if (nodeType.getCode().equals(dbData)) { return nodeType; } } throw new IllegalArgumentException("Unknown database value:" + dbData); } } 

Sur l’entité dont vous avez juste besoin:

 @Column(name = "node_type_code") 

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 append @Convert(converter = NodeTypeConverter.class) dans la colonne de la classe d’entité.

Peut-être fermer le code associé de Pascal

 @Entity @Table(name = "AUTHORITY_") public class Authority implements Serializable { public enum Right { READ(100), WRITE(200), EDITOR(300); private Integer value; private Right(Integer value) { this.value = value; } // Reverse lookup Right for getting a Key from it's values private static final Map lookup = new HashMap(); static { for (Right item : Right.values()) lookup.put(item.getValue(), item); } public Integer getValue() { return value; } public static Right getKey(Integer value) { return lookup.get(value); } }; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "AUTHORITY_ID") private Long id; @Column(name = "RIGHT_ID") private Integer rightId; public Right getRight() { return Right.getKey(this.rightId); } public void setRight(Right right) { this.rightId = right.getValue(); } } 

Je ferais le suivant:

Déclarez séparément l’énumération, dans son propre fichier:

 public enum RightEnum { READ(100), WRITE(200), EDITOR (300); private int value; private RightEnum (int value) { this.value = value; } @Override public static Etapa valueOf(Integer value){ for( RightEnum r : RightEnum .values() ){ if ( r.getValue().equals(value)) return r; } return null;//or throw exception } public int getValue() { return value; } } 

Déclarez une nouvelle entité JPA nommée Right

 @Entity public class Right{ @Id private Integer id; //FIElDS // constructor public Right(RightEnum rightEnum){ this.id = rightEnum.getValue(); } public Right getInstance(RightEnum rightEnum){ return new Right(rightEnum); } } 

Vous aurez également besoin d´un convertisseur pour recevoir ces valeurs (JPA 2.1 seulement et il y aura un problème dont je ne discuterai pas ici avec ces énumérateurs à conserver directement en utilisant le convertisseur, donc ce sera une route à sens unique).

 import mypackage.RightEnum; import javax.persistence.AtsortingbuteConverter; import javax.persistence.Converter; /** * * */ @Converter(autoApply = true) public class RightEnumConverter implements AtsortingbuteConverter{ @Override //this method shoudn´t be used, but I implemented anyway, just in case public Integer convertToDatabaseColumn(RightEnum atsortingbute) { return atsortingbute.getValue(); } @Override public RightEnum convertToEntityAtsortingbute(Integer dbData) { return RightEnum.valueOf(dbData); } } 

L’entité Autorité:

 @Entity @Table(name = "AUTHORITY_") public class Authority implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "AUTHORITY_ID") private Long id; // the **Entity** to map : private Right right; // the **Enum** to map (not to be persisted or updated) : @Column(name="COLUMN1", insertable = false, updatable = false) @Convert(converter = RightEnumConverter.class) private RightEnum rightEnum; } 

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

 autorithy.setRight( Right.getInstance( RightEnum.READ ) );//for example 

Et si vous avez besoin de comparer, vous pouvez utiliser:

 authority.getRight().equals( RightEnum.READ ); //for example 

Ce qui est plutôt cool, je pense. Ce n´est pas tout à fait correct puisque le convertisseur n´est pas conçu pour être utilisé avec enum´s. En fait, la documentation dit de ne jamais l’utiliser à cette fin, vous devriez plutôt utiliser l’annotation @Enumerated. Le problème est qu’il n’y a que deux types enum: ORDINAL ou STRING, mais ORDINAL est délicat et pas sûr.


Cependant, si cela ne vous satisfait pas, vous pouvez faire quelque chose d’un peu plus pirate et plus simple (ou pas).

Voyons voir.

Le RightEnum:

 public enum RightEnum { READ(100), WRITE(200), EDITOR (300); private int value; private RightEnum (int value) { try { this.value= value; final Field field = this.getClass().getSuperclass().getDeclaredField("ordinal"); field.setAccessible(true); field.set(this, value); } catch (Exception e) {//or use more multicatch if you use JDK 1.7+ throw new RuntimeException(e); } } @Override public static Etapa valueOf(Integer value){ for( RightEnum r : RightEnum .values() ){ if ( r.getValue().equals(value)) return r; } return null;//or throw exception } public int getValue() { return value; } } 

et l’entité Autorité

 @Entity @Table(name = "AUTHORITY_") public class Authority implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "AUTHORITY_ID") private Long id; // the **Enum** to map (to be persisted or updated) : @Column(name="COLUMN1") @Enumerated(EnumType.ORDINAL) private RightEnum rightEnum; } 

Dans cette seconde idée, ce n’est pas une situation parfaite puisque nous piratons l’atsortingbut 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.