Как использовать перечисления с JPA - PullRequest
34 голосов
/ 09 декабря 2008

У меня есть база данных системы проката фильмов. Каждый фильм имеет атрибут рейтинга. В SQL они использовали ограничение, чтобы ограничить допустимые значения этого атрибута.

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)))

Я думаю, что было бы неплохо использовать перечисление Java для сопоставления ограничения с объектным миром. Но невозможно просто принять допустимые значения из-за специального символа в «PG-13» и «NC-17». Поэтому я реализовал следующее перечисление:

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;
    ..

При использовании метода toString () направление enum -> String работает нормально, а String -> enum не работает. Я получаю следующее исключение:

[TopLink Warning]: 2008.12.09 01: 30: 57.434 - ServerSession (4729123) - Исключение [TOPLINK-116] (Oracle TopLink Essentials - 2.0.1 (сборка b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.DescriptorException Exception Описание: для значения [NC-17] в поле [FILM.RATING]. Отображение: oracle.toplink.essentials.mappings.DirectToFieldMapping [рейтинг -> FILM.RATING] Дескриптор: RelationalDescriptor (de.fhw.nsdb.entities.Film -> [DatabaseTable (пленка)])

ура

Timo

Ответы [ 10 ]

29 голосов
/ 08 января 2009

пытались ли вы сохранить порядковый номер. Сохранение строкового значения работает нормально, если у вас нет связанной с ним строки:

@Enumerated(EnumType.ORDINAL)
24 голосов
/ 23 декабря 2008

У вас проблема здесь, и это ограниченные возможности JPA, когда дело доходит до обработки перечислений. С перечислениями у вас есть два варианта:

  1. Храните их как число, равное Enum.ordinal(), что является ужасной идеей (imho); или
  2. Сохраните их как строку, равную Enum.name(). Примечание: не toString(), как вы могли бы ожидать, тем более что поведение по умолчанию Enum.toString() должно возвращать name().

Лично я думаю, что лучший вариант - (2).

Теперь у вас есть проблема в том, что вы определяете значения, которые не представляют недействительные имена экземпляров в Java (а именно, используют дефис). Итак, ваш выбор:

  • Измените ваши данные;
  • Сохранять строковые поля и неявно преобразовывать их в перечисления в ваших объектах или из них; или
  • Используйте нестандартные расширения, такие как TypeConverters.

Я бы делал их в таком порядке (с начала до конца) в порядке предпочтения.

Кто-то предложил конвертер Oracle TopLink, но вы, вероятно, используете Toplink Essentials, являющийся эталонной реализацией JPA 1.0, которая является подмножеством коммерческого продукта Oracle Toplink.

В качестве другого предложения я настоятельно рекомендую переключиться на EclipseLink . Это гораздо более полная реализация, чем Toplink Essentials, и Eclipselink будет эталонной реализацией JPA 2.0 после ее выпуска (ожидается в середине следующего года JavaOne).

7 голосов
/ 09 декабря 2008

Похоже, вам нужно добавить поддержку для пользовательского типа:

Расширение OracleAS TopLink для поддержки пользовательских преобразований типов

5 голосов
/ 09 декабря 2008
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;
    }
}

Однако я не знаю, как сделать сопоставления в аннотированной стороне TopLink.

2 голосов
/ 09 декабря 2008

Я не знаю, что такое toplink, но я предпочитаю следующее: он использует метод Rating.valueOf (String s) для отображения в другом направлении. невозможно переопределить valueOf (), поэтому вы должны придерживаться соглашения об именах java, чтобы разрешить правильный метод valueOf.

public enum Rating {

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

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

getRating выдает «читабельный» рейтинг. обратите внимание, что символ "-" не допускается в идентификаторе перечисления.

конечно, вы должны будете сохранить значения в БД как NC_17.

1 голос
/ 23 июня 2013

Проблема, я думаю, в том, что JPA никогда не задумывался с мыслью о том, что у нас уже может быть сложная существующая схема.

Я думаю, что из-за этого есть два основных недостатка, специфичных для Enum:

  1. Ограничение использования name () и ordinal (). Почему бы просто не пометить получатель с помощью @Id, как мы это делаем с @Entity?
  2. Enum обычно имеют представление в базе данных, чтобы разрешить ассоциацию со всеми видами метаданных, включая собственное имя, описательное имя, может быть что-то с локализацией и т. Д. Нам нужно простое использование Enum в сочетании с гибкостью Entity .

Помогите мне и проголосуйте за JPA_SPEC-47

0 голосов
/ 14 ноября 2017

Enum public enum ParentalControlLevelsEnum { U («U»), PG («PG»), _12 («12»), _15 («15»), _18 («18»);

private final String value;

ParentalControlLevelsEnum(final String value) {
    this.value = value;
}

public String getValue() {
    return value;
}

public static ParentalControlLevelsEnum fromString(final String value) {
    for (ParentalControlLevelsEnum level : ParentalControlLevelsEnum.values()) {
        if (level.getValue().equalsIgnoreCase(value)) {
            return level;
        }
    }
    return null;
}

}

сравнить -> Enum

открытый класс RatingComparator реализует Comparator {

public int compare(final ParentalControlLevelsEnum o1, final ParentalControlLevelsEnum o2) {
    if (o1.ordinal() < o2.ordinal()) {
        return -1;
    } else {
        return 1;
    }
}

}

0 голосов
/ 07 августа 2012

Решено !!! Где я нашел ответ: http://programming.itags.org/development-tools/65254/

Вкратце, преобразование ищет имя перечисления, а не значение атрибута 'rating'. В вашем случае: если у вас есть значения в БД "NC-17", вам нужно иметь в своем перечислении:

enum Rating {
(...)
NC-17 ("NC-17");
(...)

0 голосов
/ 04 октября 2011

используйте эту аннотацию

@Column(columnDefinition="ENUM('User', 'Admin')")
0 голосов
/ 09 декабря 2008

Как насчет этого

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;
}

при этом вы используете рейтинги в качестве строки как для вашей БД, так и для сущности, но для всех остальных используйте перечисление.

...