Как использовать перечисления с 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 для сопоставления ограничения с миром объектов. Но невозможно просто взять допустимые значения из-за специального символа в «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)--Exception [TOPLINK-116] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.DescriptorException Exception Description: No conversion value provided for the value [NC-17] in field [FILM.RATING]. Mapping: oracle.toplink.essentials.mappings.DirectToFieldMapping[rating-->FILM.RATING] Descriptor: RelationalDescriptor(de.fhw.nsdb.entities.Film --> [DatabaseTable(FILM)])

ваше здоровье

тимо

вы намеренно пропустили атрибут @Column для поля? Без него было бы трудно что-либо выдержать ...

Yuval 09.12.2008 15:53

Вам понадобится статическая хэш-карта ваших перечислений внутри перечисления и статический метод getByRating.

JeeBee 09.12.2008 17:06
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
35
2
73 163
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Я не знаю внутреннего устройства 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.

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

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 как в своей БД, так и в сущности, но используйте перечисление для всего остального.

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.

Ответ принят как подходящий

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

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

У вас есть проблема, и это ограниченные возможности 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 в середине следующего года).

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

@Enumerated(EnumType.ORDINAL)

Я не могу поверить, что у этого нет больше голосов, учитывая, что @Enumerated - это часть JPA1. Лично я думаю, что EnumType.STRING был бы лучше по причинам, указанным Клетусом.

Powerlord 15.05.2012 23:39

@electrotype Да, технически это работает, но это далеко не лучшая практика. Хранить перечисление по его числовому значению - УЖАСНАЯ идея. Очень простые изменения, которые не изменяют функциональность вашего кода, могут легко сделать все ваши постоянные данные недействительными. Например, изменение порядка значений перечисления или добавление нового значения в начало списка.

spaaarky21 30.08.2012 02:14

@ spaaarky21 Я согласен с этим в большинстве случаев, однако, сохраняя значения, которые никогда не изменятся, например, будние дни, вы также можете сохранить порядковый номер (и сохранить его как TINYINT (1) в случае будних дней).

Jelle Blaauw 18.09.2017 12:54

@JelleBlaauw Я бы делал это везде, как на конвенциях. Даже день недели может быть проблематичным. В неделе будет только семь дней, но разные библиотеки относятся к ним по-разному. Основные различия заключаются в том, начинается ли неделя с воскресенья (например, Java Calendar) или понедельника (например, Java 8 DayOfWeek), и основаны ли значения на 0 (например, порядковые номера перечисления) или на основе 1 (например, константы Joda). Представьте, что вы меняете свой проект с одной библиотеки на другую, а разработчик из лучших побуждений меняет порядок значений перечисления DayOfWeek, чтобы они соответствовали друг другу.

spaaarky21 18.09.2017 21:26

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

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

Я почти уверен, что это не сработает. Сообщение JPA о том, как определяется столбец, не даст указание искать значение для базы данных другим способом. В Hibernate определение столбца помогает устранить несоответствия типов - например, если Hibernate считает, что для столбца следует использовать один тип BLOB-объекта, а в базе данных - другой.

spaaarky21 20.02.2012 12:42

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

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

enum Рейтинг {
(...)
NC-17 ("NC-17");
(...)

Голосовать против, потому что - не разрешено в именах перечислений, как указано в OP.

lxs 10.08.2012 19:08

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

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

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

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

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

}

Используя ваш существующий enum Rating. Вы можете использовать AttributeCoverters.

@Converter(autoApply = true)
public class RatingConverter implements AttributeConverter<Rating, String> {

    @Override
    public String convertToDatabaseColumn(Rating rating) {
        if (rating == null) {
            return null;
        }
        return rating.toString();
    }

    @Override
    public Rating convertToEntityAttribute(String code) {
        if (code == null) {
            return null;
        }

        return Stream.of(Rating.values())
          .filter(c -> c.toString().equals(code))
          .findFirst()
          .orElseThrow(IllegalArgumentException::new);
    }
}

Другие вопросы по теме