Перечислить карту в JPA с фиксированными значениями? - PullRequest
176 голосов
/ 02 мая 2010

Я ищу разные способы отображения перечисления с использованием JPA. Я особенно хочу установить целочисленное значение для каждой записи перечисления и сохранить только целочисленное значение.

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

  public enum Right {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      Right(int value) { this.value = value; }

      public int getValue() { return value; }
  };

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;

  // the enum to map : 
  private Right right;
}

Простым решением является использование аннотации Enumerated с EnumType.ORDINAL:

@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;

Но в этом случае JPA отображает индекс перечисления (0,1,2), а не значение, которое я хочу (100,200,300).

Два решения, которые я нашел, не кажутся простыми ...

Первое решение

Решение, предложенное здесь , использует @PrePersist и @PostLoad для преобразования enum в другое поле и помечает поле enum как переходное:

@Basic
private int intValueForAnEnum;

@PrePersist
void populateDBFields() {
  intValueForAnEnum = right.getValue();
}

@PostLoad
void populateTransientFields() {
  right = Right.valueOf(intValueForAnEnum);
}

Второе решение

Второе решение , предложенное здесь , предложило универсальный объект преобразования, но все еще кажется тяжелым и ориентированным на спящий режим (похоже, @Type не существует в Java EE):

@Type(
    type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
    parameters = {
            @Parameter(
                name  = "enumClass",                      
                value = "Authority$Right"),
            @Parameter(
                name  = "identifierMethod",
                value = "toInt"),
            @Parameter(
                name  = "valueOfMethod",
                value = "fromInt")
            }
)

Есть ли другие решения?

Я имею в виду несколько идей, но не знаю, существуют ли они в JPA:

  • использовать методы setter и getter правого члена класса Authority при загрузке и сохранении объекта Authority
  • эквивалентной идеей было бы сообщить JPA, каковы методы Right enum для преобразования enum в int и int в enum
  • Поскольку я использую Spring, есть ли способ указать JPA использовать определенный конвертер (RightEditor)?

Ответы [ 7 ]

162 голосов
/ 02 мая 2010

Для версий более ранних, чем JPA 2.1, JPA предоставляет только два способа работы с перечислениями: по name или по ordinal. И стандартный JPA не поддерживает пользовательские типы. Итак:

  • Если вы хотите выполнять пользовательские преобразования типов, вам придется использовать расширение поставщика (с Hibernate UserType, EclipseLink Converter и т. Д.). (второе решение). ~ Или ~
  • Вам придется использовать трюки @PrePersist и @PostLoad (первое решение). ~ Или ~
  • Аннотировать геттер и сеттер, берущие и возвращающие значение int ~ или ~
  • Используйте целочисленный атрибут на уровне сущности и выполняйте перевод в методах получения и установки.

Я проиллюстрирую последний вариант (это базовая реализация, настройте его по мере необходимости):

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

    public enum Right {
        READ(100), WRITE(200), EDITOR (300);

        private int value;

        Right(int value) { this.value = value; }    

        public int getValue() { return value; }

        public static Right parse(int id) {
            Right right = null; // Default
            for (Right item : Right.values()) {
                if (item.getValue()==id) {
                    right = item;
                    break;
                }
            }
            return right;
        }

    };

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "AUTHORITY_ID")
    private Long id;

    @Column(name = "RIGHT_ID")
    private int rightId;

    public Right getRight () {
        return Right.parse(this.rightId);
    }

    public void setRight(Right right) {
        this.rightId = right.getValue();
    }

}
64 голосов
/ 12 января 2014

Теперь это возможно с JPA 2.1:

@Column(name = "RIGHT")
@Enumerated(EnumType.STRING)
private Right right;

Более подробная информация:

16 голосов
/ 26 января 2015

Наилучшим подходом было бы сопоставление уникального идентификатора с каждым типом перечисления, таким образом избегая ловушек ORDINAL и STRING. См. Этот пост , в котором описаны 5 способов сопоставления перечисления.

взято по ссылке выше:

1 & 2. Использование @ Enumerated

В настоящее время существует 2 способа отображения перечислений внутри объектов JPA с помощью аннотации @Enumerated. К сожалению, и EnumType.STRING, и EnumType.ORDINAL имеют свои ограничения.

Если вы используете EnumType.String, то переименование одного из типов перечислений приведет к тому, что значение перечисления будет не синхронизировано со значениями, сохраненными в базе данных. Если вы используете EnumType.ORDINAL, то удаление или изменение порядка типов в вашем перечислении приведет к тому, что значения, сохраненные в базе данных, будут сопоставлены с неправильными типами перечислений.

Оба эти варианта хрупки. Если перечисление будет изменено без выполнения миграции базы данных, вы можете поставить под угрозу целостность ваших данных.

3. Обратные вызовы жизненного цикла

Возможное решение - использовать аннотации обратного вызова жизненного цикла JPA, @PrePersist и @PostLoad. Это кажется довольно уродливым, поскольку теперь у вас будет две переменные в вашей сущности Один отображает значение, хранящееся в базе данных, а другой - фактическое перечисление.

4. Назначение уникального идентификатора каждому типу перечисления

Предпочтительным решением является сопоставление вашего перечисления с фиксированным значением или идентификатором, определенным в перечислении. Отображение на предопределенное фиксированное значение делает ваш код более надежным. Любое изменение порядка типов перечислений или рефакторинг имен не вызовет каких-либо неблагоприятных последствий.

5. Использование Java EE7 @ Convert

Если вы используете JPA 2.1, у вас есть возможность использовать новую аннотацию @Convert. Это требует создания класса конвертера с аннотацией @Converter, внутри которого вы будете определять, какие значения сохраняются в базе данных для каждого типа перечисления. Внутри вашей сущности вы затем аннотируете свое перечисление @ Convert.

Мои предпочтения: (номер 4)

Причиной, по которой я предпочитаю определять свои идентификаторы в перечислении, а не использовать конвертер, является хорошая инкапсуляция. Только тип enum должен знать его идентификатор, и только объект должен знать, как он отображает enum в базу данных.

См. Оригинальный код post для примера кода.

14 голосов
/ 11 августа 2014

Из JPA 2.1 вы можете использовать AttributeConverter .

Создайте перечислимый класс следующим образом:

public enum NodeType {

    ROOT("root-node"),
    BRANCH("branch-node"),
    LEAF("leaf-node");

    private final String code;

    private NodeType(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

И создайте конвертер вот так:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

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

    @Override
    public String convertToDatabaseColumn(NodeType nodeType) {
        return nodeType.getCode();
    }

    @Override
    public NodeType convertToEntityAttribute(String dbData) {
        for (NodeType nodeType : NodeType.values()) {
            if (nodeType.getCode().equals(dbData)) {
                return nodeType;
            }
        }

        throw new IllegalArgumentException("Unknown database value:" + dbData);
    }
}

На сущности вам просто нужно:

@Column(name = "node_type_code")

Удача с @Converter(autoApply = true) может варьироваться в зависимости от контейнера, но проверена на работу с Wildfly 8.1.0. Если это не работает, вы можете добавить @Convert(converter = NodeTypeConverter.class) к столбцу класса сущности.

10 голосов
/ 23 июня 2013

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

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

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

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

Не будет ли это более элегантно, чем использование @Converter для решения проблемы?

// Note: this code won't work!!
// it is just a sample of how I *would* want it to work!
@Enumerated
public enum Language {
  ENGLISH_US("en-US"),
  ENGLISH_BRITISH("en-BR"),
  FRENCH("fr"),
  FRENCH_CANADIAN("fr-CA");
  @ID
  private String code;
  @Column(name="DESCRIPTION")
  private String description;

  Language(String code) {
    this.code = code;
  }

  public String getCode() {
    return code;
  }

  public String getDescription() {
    return description;
  }
}
3 голосов
/ 31 марта 2013

Возможно закрыть связанный код Паскаля

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

    public enum Right {
        READ(100), WRITE(200), EDITOR(300);

        private Integer value;

        private Right(Integer value) {
            this.value = value;
        }

        // Reverse lookup Right for getting a Key from it's values
        private static final Map<Integer, Right> lookup = new HashMap<Integer, Right>();
        static {
            for (Right item : Right.values())
                lookup.put(item.getValue(), item);
        }

        public Integer getValue() {
            return value;
        }

        public static Right getKey(Integer value) {
            return lookup.get(value);
        }

    };

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "AUTHORITY_ID")
    private Long id;

    @Column(name = "RIGHT_ID")
    private Integer rightId;

    public Right getRight() {
        return Right.getKey(this.rightId);
    }

    public void setRight(Right right) {
        this.rightId = right.getValue();
    }

}
1 голос
/ 19 сентября 2014

Я бы сделал следующее:

Объявите отдельно перечисление в своем собственном файле:

public enum RightEnum {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      private RightEnum (int value) { this.value = value; }


      @Override
      public static Etapa valueOf(Integer value){
           for( RightEnum r : RightEnum .values() ){
              if ( r.getValue().equals(value))
                 return r;
           }
           return null;//or throw exception
     }

      public int getValue() { return value; }


}

Объявить новый объект JPA с именем Right

@Entity
public class Right{
    @Id
    private Integer id;
    //FIElDS

    // constructor
    public Right(RightEnum rightEnum){
          this.id = rightEnum.getValue();
    }

    public Right getInstance(RightEnum rightEnum){
          return new Right(rightEnum);
    }


}

Вам также понадобится конвертер для получения этих значений (только JPA 2.1, и есть проблема, которую я не буду обсуждать здесь с этими enum'ами, которые будут напрямую сохраняться с помощью конвертера, так что это будет только односторонняя дорога )

import mypackage.RightEnum;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

/**
 * 
 * 
 */
@Converter(autoApply = true)
public class RightEnumConverter implements AttributeConverter<RightEnum, Integer>{

    @Override //this method shoudn´t be used, but I implemented anyway, just in case
    public Integer convertToDatabaseColumn(RightEnum attribute) {
        return attribute.getValue();
    }

    @Override
    public RightEnum convertToEntityAttribute(Integer dbData) {
        return RightEnum.valueOf(dbData);
    }

}

Субъект Органа:

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {


  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;

  // the **Entity** to map : 
  private Right right;

  // the **Enum** to map (not to be persisted or updated) : 
  @Column(name="COLUMN1", insertable = false, updatable = false)
  @Convert(converter = RightEnumConverter.class)
  private RightEnum rightEnum;

}

Поступая таким образом, вы не можете установить непосредственно поле enum. Тем не менее, вы можете установить поле Right в Authority, используя

autorithy.setRight( Right.getInstance( RightEnum.READ ) );//for example

А если вам нужно сравнить, вы можете использовать:

authority.getRight().equals( RightEnum.READ ); //for example

Что довольно круто, я думаю. Это не совсем правильно, так как конвертер не предназначен для использования с enum. На самом деле, в документации сказано, что никогда не используйте его для этой цели, вместо этого вы должны использовать аннотацию @Enumerated. Проблема в том, что существует только два типа enum: ORDINAL или STRING, но ORDINAL сложен и небезопасен.


Однако, если вас это не устраивает, вы можете сделать что-то более хакерское и простое (или нет).

Посмотрим.

The RightEnum:

public enum RightEnum {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      private RightEnum (int value) { 
            try {
                  this.value= value;
                  final Field field = this.getClass().getSuperclass().getDeclaredField("ordinal");
                  field.setAccessible(true);
                  field.set(this, value);
             } catch (Exception e) {//or use more multicatch if you use JDK 1.7+
                  throw new RuntimeException(e);
            }
      }


      @Override
      public static Etapa valueOf(Integer value){
           for( RightEnum r : RightEnum .values() ){
              if ( r.getValue().equals(value))
                 return r;
           }
           return null;//or throw exception
     }

      public int getValue() { return value; }


}

и орган управления

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {


  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;


  // the **Enum** to map (to be persisted or updated) : 
  @Column(name="COLUMN1")
  @Enumerated(EnumType.ORDINAL)
  private RightEnum rightEnum;

}

В этой второй идее, это не идеальная ситуация, поскольку мы взломали порядковый атрибут, но это гораздо меньшая кодировка.

Я думаю, что спецификация JPA должна включать EnumType.ID, где поле значения enum должно быть аннотировано с помощью своего рода аннотации @EnumId.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...