9.Как мапятся Enum`ы?

9.Как мапятся Enum`ы?

Unknown

По порядковым номерам

Если мы сохраняем в БД сущность, у которой есть поле-перечисление (Enum), то в таблице этой сущности создаётся колонка для значений этого перечисления и по умолчанию в ячейки сохраняется порядковый номер этого перечисления (ordinal).

public enum MyEnum {

  ConstA, ConstB, ConstC

}

@Entity

public class MyEntity {

  @Id

  private long myId;

  private MyEnum myEnum;

  public MyEntity() {

  }

  public MyEntity(long myId, MyEnum myEnum) {

      this.myId = myId;

      this.myEnum = myEnum;

  }

    .............

}

В  JPA  типы  Enum  могут  быть  помечены  аннотацией  @Enumerated,  которая может  принимать  в  качестве  атрибута  EnumType.ORDINAL  или  EnumType.STRING, определяющий, отображается ли перечисление (enum) на столбец с типом Integer или String соответственно.

@Enumerated(EnumType.ORDINAL) - значение по умолчанию, говорит о том, что в базе будут храниться порядковые номера Enum (0, 1, 2…). Проблема с этим типом отображения возникает, когда нам нужно изменить наш Enum. Если мы добавим новое значение  в  середину  или  просто  изменим  порядок  перечисления,  мы  сломаем существующую модель данных. Такие проблемы могут быть трудно уловимыми, и нам придется обновлять все записи базы данных.

По именам

@Enumerated(EnumType.STRING) - означает, что в базе будут храниться имена Enum.  С  @Enumerated(EnumType.STRING)  мы  можем  безопасно  добавлять новые значения перечисления или изменять порядок перечисления. Однако переименование значения enum все равно нарушит работу базы данных. Кроме того, даже несмотря на то, что это представление данных гораздо более читаемо по сравнению с параметром @Enumerated(EnumType.ORDINAL),  оно  потребляет  намного  больше  места,  чем необходимо. Это может оказаться серьезной проблемой, когда нам нужно иметь дело с большим объемом данных.

@PostLoad и @PrePersist

Другой вариант - использование стандартных методов обратного вызова из JPA.

Мы  можем  смапить  наши  перечисления  в  БД  и  обратно  в  методах  с  аннотациями @PostLoad и @PrePersist.

Идея  состоит  в  том,  чтобы  в  сущности  иметь  не  только  поле  с  Enum,  но и вспомогательное поле. Поле с Enum аннотируем @Transient, а в БД будет храниться значение  из  вспомогательного  поля.  Создадим  Enum  с  полем  priority.  содержащем числовое значение приоритета:

public enum Priority {

    LOW(100), MEDIUM(200), HIGH(300);

    private int priority;

    private Priority(int priority) {

        this.priority = priority;

    }

    public int getPriority() {

        return priority;

    }

    public static Priority of(int priority) {

        return Stream.of(Priority.values())

          .filter(p -> p.getPriority() == priority)

          .findFirst()

          .orElseThrow(IllegalArgumentException::new);

    }

}

Мы добавили метод Priority.of(), чтобы упростить получение экземпляра Priority на основе его значения int. Теперь, чтобы использовать его в нашем классе Article, нам нужно добавить два атрибута и реализовать методы обратного вызова:

@Entity

public class Article {

    @Id

    private int id;

    private String title;

    @Enumerated(EnumType.ORDINAL)

    private Status status;

    @Enumerated(EnumType.STRING)

    private Type type;

    @Basic

    private int priorityValue;

    @Transient

    private Priority priority;

    @PostLoad

    void fillTransient() {

        if (priorityValue > 0) {

            this.priority = Priority.of(priorityValue);  27

        }

    }

 

    @PrePersist

    void fillPersistent() {

        if (priority != null) {

            this.priorityValue = priority.getPriority();

        }

    }

}

Несмотря на то, что этот вариант дает нам бОльшую гибкость по сравнению с ранее описанными решениями, он не идеален. Просто кажется неправильным иметь в сущности целых два атрибута, представляющих одно перечисление. Кроме того, если мы используем этот вариант, мы не сможем использовать значение Enum в запросах JPQL.

Converter

В  JPA  с версии  2.1  можно  использовать  Converter для  конвертации  Enum’а  в некое его значение для сохранения в БД и получения из БД. Все, что нам нужно сделать, это  создать  новый  класс,  который  реализует  javax.persistence.AttributeConverter  и аннотировать его с помощью @Converter.

public enum Category {

    SPORT("S"), MUSIC("M"), TECHNOLOGY("T");

    private String code;

    private Category(String code) {

        this.code = code;

    }

    public String getCode() {

        return code;

    }

}

@Entity

public class Article {

    @Id

    private int id;

    private String title;

    @Basic

    private int priorityValue;

    @Transient

    private Priority priority;

    private Category category;

}

@Converter(autoApply = true)

public class CategoryConverter implements AttributeConverter<Category, String> {

     @Override

    public String convertToDatabaseColumn(Category category) {  28

        if (category == null) {

            return null;

        }

        return category.getCode();

    }

    @Override

    public Category convertToEntityAttribute(String code) {

        if (code == null) {

            return null;

        }

         return Stream.of(Category.values())

          .filter(c -> c.getCode().equals(code))

          .findFirst()

          .orElseThrow(IllegalArgumentException::new);

    }

}

Мы установили @Converter(autoApply=true), чтобы JPA автоматически применял логику преобразования ко всем сопоставленным атрибутам типа Category. В противном случае нам пришлось бы поместить аннотацию @Converter непосредственно над полем Category у каждой сущности, где оно имеется.

В результате в столбце таблицы будут храниться значения: "S", "M" или "T". 

Как  мы  видим,  мы  можем  просто  установить  наши  собственные  правила преобразования  перечислений  в  соответствующие  значения  базы  данных,  если  мы используем интерфейс AttributeConverter. Более того, мы можем безопасно добавлять новые  значения  enum  или  изменять  существующие,  не  нарушая  уже  сохраненные данные.  Это  решение  просто  в  реализации  и  устраняет  все  недостатки  с @Enumerated(EnumType.ORDINAL),  @Enumerated(EnumType.STRING)  и  методами обратного вызова.


Предыдущий вопрос: 8. Какие три стратегии маппинга при наследовании сущностей (Entity Inheritance Mapping Strategies) описаны в JPA?

Следующий вопрос: 10. Как мапятся даты (до Java 8 и после)?

Все вопросы по теме: список

Все темы: список

Вопросы/замечания/предложения/нашли ошибку: напишите мне


Report Page