web-dev-qa-db-fra.com

Comment utiliser les énumérations avec JPA

J'ai une base de données existante d'un système de location de films. Chaque film a un attribut de notation. En SQL, ils ont utilisé une contrainte pour limiter les valeurs autorisées de cet attribut.

CONSTRAINT film_rating_check CHECK 
    ((((((((rating)::text = ''::text) OR 
          ((rating)::text = 'G'::text)) OR 
          ((rating)::text = 'PG'::text)) OR 
          ((rating)::text = 'PG-13'::text)) OR 
          ((rating)::text = 'R'::text)) OR 
          ((rating)::text = 'NC-17'::text)))

Je pense que ce serait bien d'utiliser une énumération Java pour mapper la contrainte dans le monde des objets. Mais il n'est pas possible de simplement prendre les valeurs autorisées en raison du caractère spécial dans "PG-13" et "NC-17". J'ai donc implémenté l'énumération suivante:

public enum Rating {

    UNRATED ( "" ),
    G ( "G" ), 
    PG ( "PG" ),
    PG13 ( "PG-13" ),
    R ( "R" ),
    NC17 ( "NC-17" );

    private String rating;

    private Rating(String rating) {
        this.rating = rating;
    }

    @Override
    public String toString() {
        return rating;
    }
}

@Entity
public class Film {
    ..
    @Enumerated(EnumType.STRING)
    private Rating rating;
    ..

Avec la méthode toString (), la direction enum -> String fonctionne correctement, mais String -> enum ne fonctionne pas. J'obtiens l'exception suivante:

[Avertissement TopLink]: 2008.12.09 01: 30: 57.434 - ServerSession (4729123) - Exception [TOPLINK-116] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): Oracle.toplink.essentials.exceptions.DescriptorException Exception Description: Aucune valeur de conversion fournie pour la valeur [NC-17] dans le champ [FILM.RATING]. Mappage: Oracle.toplink.essentials.mappings.DirectToFieldMapping [classement -> FILM.RATING] Descripteur: RelationalDescriptor (de.fhw.nsdb.entities.Film -> [DatabaseTable (FILM)])

à votre santé

timo

35
Timo

On dirait que vous devez ajouter la prise en charge d'un type personnalisé:

Extension d'OracleAS TopLink pour prendre en charge les conversions de types personnalisés

8
Dan Vinton

avez-vous essayé de stocker la valeur ordinale. Stocker la valeur de chaîne fonctionne correctement si vous n'avez pas de chaîne associée à la valeur:

@Enumerated(EnumType.ORDINAL)
30
aledbf

Vous avez un problème ici et ce sont les capacités limitées de JPA lorsqu'il s'agit de gérer les énumérations. Avec les énumérations, vous avez deux choix:

  1. Stockez-les sous la forme d'un nombre égal à Enum.ordinal(), ce qui est une idée terrible (à mon humble avis); ou
  2. Stockez-les sous forme de chaîne égale à Enum.name(). Remarque: pas toString() comme on pourrait s'y attendre, d'autant plus que le comportement par défaut de Enum.toString() est de retourner name().

Personnellement, je pense que la meilleure option est (2).

Vous avez maintenant un problème en ce que vous définissez des valeurs qui ne représentent pas les noms d'instance vailid dans Java (à savoir en utilisant un trait d'union). Ainsi, vos choix sont:

  • Modifiez vos données;
  • Conserver les champs de chaîne et les convertir implicitement vers ou à partir d'énumérations dans vos objets; ou
  • Utilisez des extensions non standard comme TypeConverters.

Je les ferais dans cet ordre (du premier au dernier) comme ordre de préférence.

Quelqu'un a suggéré le convertisseur d'Oracle TopLink, mais vous utilisez probablement Toplink Essentials, étant l'implémentation de référence JPA 1.0, qui est un sous-ensemble du produit commercial Oracle Toplink.

Comme autre suggestion, je recommande fortement de passer à EclipseLink . Il s'agit d'une implémentation beaucoup plus complète que Toplink Essentials et Eclipselink sera l'implémentation de référence de JPA 2.0 lors de sa sortie (prévue par JavaOne au milieu de l'année prochaine).

25
cletus
public enum Rating {

    UNRATED ( "" ),
    G ( "G" ), 
    PG ( "PG" ),
    PG13 ( "PG-13" ),
    R ( "R" ),
    NC17 ( "NC-17" );

    private String rating;

    private static Map<String, Rating> ratings = new HashMap<String, Rating>();
    static {
        for (Rating r : EnumSet.allOf(Rating.class)) {
            ratings.put(r.toString(), r);
        }
    }

    private static Rating getRating(String rating) {
        return ratings.get(rating);
    }

    private Rating(String rating) {
        this.rating = rating;
    }

    @Override
    public String toString() {
        return rating;
    }
}

Cependant, je ne sais pas comment faire les mappages dans le côté annoté de TopLink.

5
JeeBee

je ne connais pas les éléments internes de toplink, mais ma supposition éclairée est la suivante: il utilise la méthode Rating.valueOf (String s) pour mapper dans l'autre sens. il n'est pas possible de remplacer valueOf (), vous devez donc vous en tenir à la convention de dénomination de Java, pour autoriser une méthode valueOf correcte.

public enum Rating {

    UNRATED,
    G, 
    PG,
    PG_13 ,
    R ,
    NC_17 ;

    public String getRating() {
        return name().replace("_","-");;
    }
}

getRating produit la note "lisible par l'homme". notez que le caractère "-" n'est pas autorisé dans l'identificateur d'énumération.

vous devrez bien sûr enregistrer les valeurs dans la base de données sous NC_17.

2
Andreas Petersson

Le problème est, je pense, que JPA n'a jamais été enthousiasmé par l'idée que nous pourrions avoir un schéma préexistant complexe déjà en place.

Je pense qu'il y a deux principales lacunes en résultant, spécifiques à 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. Les Enum ont généralement une représentation dans la base de données 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 de la facilité d'utilisation d'un Enum combinée à la flexibilité d'une entité.

Aidez ma cause et votez JPA_SPEC-47

1
YoYo

Et ça

public String getRating{  
   return rating.toString();
}

pubic void setRating(String rating){  
   //parse rating string to rating enum
   //JPA will use this getter to set the values when getting data from DB   
}  

@Transient  
public Rating getRatingValue(){  
   return rating;
}

@Transient  
public Rating setRatingValue(Rating rating){  
   this.rating = rating;
}

avec cela, vous utilisez les évaluations en tant que chaîne à la fois sur votre base de données et votre entité, mais utilisez l'énumération pour tout le reste.

0
Mg.