web-dev-qa-db-fra.com

Mappage des types énumérés JPA. Meilleure approche

Comme d'habitude Java les types d'énumération ont des codes et une description de nom correspondants. Et Java les classes qui contiennent de tels champs, les contiennent comme Enum:

public enum MyEnum{
    SOMEINSTANCE(1, "test1"),
    SOMEINSTANCE(2, "test2");

    private final int code;
    private final String name;
    private MyEnum(int code, String name){
        this.code = code;
        this.name = name;
    }
    ... helper getter for code and name
}

@Entity
puclic class EnumHolder{
    private MyEnum myEnum;
}

Je suis un débutant à JPA, mais je souhaite que la table 'myEnums' ressemble à:

code int not null, name varchar(50) not null)

Et dans ma table enumHolder, je veux avoir le champ myEnumCode qui pointe vers la table myEnums.

L'utilisation de currenlty a pris en charge EnumType.ORDINAL et EnumType.STRING Je suppose que ce n'est pas une bonne idée.

Et une autre question. Comment puis-je remplir le tableau myEnums en utilisant les données de classe Java MyEnum? Comment feriez-vous? La meilleure approche s'il vous plaît.

PS: voici des solutions que je peux vous proposer:

Supposons qu'il existe une table myEnum avec code et un nom fields. Java MyEnum enum, qui est décrit dans la question. enumHolder table doit avoir myEnumCode référence à myEnum.code champ. Veuillez commenter la solution si vous n'êtes pas d'accord.

@Entity
@Access(AccessType.FIELD)
public class EnumHolder {
    @Id private int id;
    @Transient private MyEnum myEnum;
    …
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public MyEnum getMyEnum() { return MyEnum; }
    public void setMyEnum(MyEnum myEnum) { this.myEnum = myEnum; }

    @Access(AccessType.PROPERTY) @Column(name="myEnumCode")
    protected int getMyEnumForDb() {
        return myEnum.getCode();
    }

    protected void setMyEnumForDb(int enumCode) {
        myEnum = MyEnum.getByCode( enumCode);
    }
…
}

Bien sûr, il y a des inconvénients ici. Mais pour le moment, je ne vois pas la meilleure approche. Les alternatives avec EnumType.ORDINAL et EnumType.STRING veuillez ne pas offrir. Je ne veux pas écrire ici tous les problèmes qui peuvent exister avec son utilisation (dans Effective Java il est décrit concernant l'utilisation des ordinaux). En utilisant EnumType.STRING Je n'aime aucune des deux car il le fait ne pas autoriser la description dans la base de données et la demander à db.

Concernant la base de données fillind. Je pense qu'il n'est pas difficile d'écrire un script, qui efface la table myEnum puis pour chaque Java fait l'insertion dans la table. Et toujours le faire pendant une phase de déploiement .

33
Alexandr

La meilleure approche serait de mapper un ID unique à chaque type d'énumération, évitant ainsi les pièges d'ORDINAL et de STRING. Voir ceci 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 au sein de vos entités JPA à l'aide de l'annotation @Enumerated. Malheureusement, EnumType.STRING et EnumType.ORDINAL ont leurs limites.

Si vous utilisez EnumType.String, le fait de renommer l'un de vos types d'énumération entraînera une 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 pouvez compromettre l'intégrité de vos données.

3. Rappels du 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 base de données et l'autre, l'énumération réelle.

4. Mappage d'un 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 négatif.

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. Au sein de 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, 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 post pour l'exemple de code.

49
Chris Ritchie

Vous pouvez ajouter un champ énuméré à votre entité en utilisant @Enumerated . Le plus important ici est que vous voulez avoir votre gâteau et le manger aussi - pour des raisons de cohérence, il serait préférable de choisir soit EnumType.ORDINAL ou EnumType.STRING.

Les deux ont leurs avantages et leurs inconvénients, qui sont répertoriés sur ce site . La grande différence semble être avec la commande - si vous avez EnumType.ORDINAL set, vous rencontriez des problèmes lors de la mise à jour de votre énumération.

Je vous encourage à repenser la façon dont vous souhaitez concevoir votre table. La chaîne et l'entier stockent essentiellement les mêmes informations - quelle est la valeur énumérée - et vous pouvez les récupérer de la base de données sans trop de bruit. Il serait préférable d'avoir un id comme clé primaire sur la table, puis d'avoir votre type énuméré sous forme de chaîne ou de valeur ordinale, en tenant compte des mises en garde du lien ci-dessus.

12
Makoto
@Column(name = "MY_TYPE")
@Enumerated(EnumType.STRING)
private MyType type;

enum MyType {
type_1
type_2
}

puis créez votre colonne comme suit:

TYPE_ID  VARCHAR(20) NOT NULL DEFAULT 'TYPE_1' CHECK (TYPE_ID IN ('TYPE_1', 'TYPE_2')))

cela a résolu mon problème.

2
Monsif Mabrouk