Подтвердить что ты не робот

Как использовать перечисления с JPA

У меня есть существующая база данных системы проката фильмов. Каждый фильм имеет атрибут рейтинга. В 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 для сопоставления ограничения в мир объектов. Но просто невозможно принять допустимые значения из-за специального char в "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]: 2008.12.09 01: 30: 57.434 - ServerSession (4729123) - Исключение [TOPLINK-116] (Oracle TopLink Essentials - 2.0.1 (Build 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

4b9b3361

Ответ 2

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

@Enumerated(EnumType.ORDINAL)

Ответ 3

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

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

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

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

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

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

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

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

Ответ 4

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.

Ответ 5

Я не знаю внутренних компонентов 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 создает "удобочитаемый" рейтинг. обратите внимание, что chanracter "-" не разрешен в идентификаторе перечисления.

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

Ответ 6

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

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

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

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

Ответ 7

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

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

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

Ответ 8

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

@Column(columnDefinition="ENUM('User', 'Admin')")

Ответ 9

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

}

compare → Enum

public class RatingComparator реализует Comparator {

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

}

Ответ 10

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

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

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