Коллекция СПД карту перечислений


есть ли способ в JPA сопоставить коллекцию перечислений в классе сущностей? Или единственное решение-обернуть Enum с другим классом домена и использовать его для сопоставления коллекции?

@Entity
public class Person {
    public enum InterestsEnum {Books, Sport, etc...  }
    //@???
    Collection<InterestsEnum> interests;
}

Я использую реализацию Hibernate JPA, но, конечно, предпочел бы реализацию агностического решения.

5 65

5 ответов:

С помощью Hibernate вы можете сделать

@CollectionOfElements(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;

ссылка в ответе Энди является отличной отправной точкой для сопоставления коллекций "не-сущностных" объектов в JPA 2, но не совсем завершена, когда речь заходит о сопоставлении перечислений. Вот что я придумал вместо.

@Entity
public class Person {
    @ElementCollection(targetClass=InterestsEnum.class)
    @Enumerated(EnumType.STRING) // Possibly optional (I'm not sure) but defaults to ORDINAL.
    @CollectionTable(name="person_interest")
    @Column(name="interest") // Column name in person_interest
    Collection<InterestsEnum> interests;
}

я смог сделать это таким простым способом:

@ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;

требуется активная загрузка, чтобы избежать ошибки ленивой загрузки inizializing, как объяснено здесь.

я использую небольшую модификацию java.утиль.RegularEnumSet, чтобы иметь постоянный EnumSet:

@MappedSuperclass
@Access(AccessType.FIELD)
public class PersistentEnumSet<E extends Enum<E>> 
    extends AbstractSet<E> {
  private long elements;

  @Transient
  private final Class<E> elementType;

  @Transient
  private final E[] universe;

  public PersistentEnumSet(final Class<E> elementType) {
    this.elementType = elementType;
    try {
      this.universe = (E[]) elementType.getMethod("values").invoke(null);
    } catch (final ReflectiveOperationException e) {
      throw new IllegalArgumentException("Not an enum type: " + elementType, e);
    }
    if (this.universe.length > 64) {
      throw new IllegalArgumentException("More than 64 enum elements are not allowed");
    }
  }

  // Copy everything else from java.util.RegularEnumSet
  // ...
}

этот класс теперь является основой для всех моих наборов перечислений:

@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
  public InterestsSet() {
    super(InterestsEnum.class);
  }
}

и которые я могу использовать в моей сущности:

@Entity
public class MyEntity {
  // ...
  @Embedded
  @AttributeOverride(name="elements", column=@Column(name="interests"))
  private InterestsSet interests = new InterestsSet();
}

плюсы:

  • работа с типобезопасным и производительным перечислением, установленным в вашем коде (см. java.util.EnumSet описание)
  • набор представляет собой только один числовой столбец в база данных
  • все просто JPA (нет конкретного поставщика пользовательские типы)
  • простое (и короткое) объявление новых полей того же типа, по сравнению с другими решениями

недостатки:

  • дублирование кода (RegularEnumSet и PersistentEnumSet почти то же самое)
    • вы можете обернуть результат EnumSet.noneOf(enumType) в своем PersistenEnumSet, объявить AccessType.PROPERTY и обеспечить два метода доступа, которые используют отражение для чтения и записи elements поле
  • дополнительный класс набора необходим для каждого класса перечисления, который должен храниться в постоянном наборе
    • если ваш поставщик сохраняемости поддерживает встроенные файлы без открытого конструктора, вы можете добавить @Embeddable до PersistentEnumSet и падение экстра-класса (... interests = new PersistentEnumSet<>(InterestsEnum.class);)
  • вы должны использовать @AttributeOverride, как указано в моем примере, если у вас есть больше чем один PersistentEnumSet в сущности (в противном случае оба будут храниться в одном столбце "элементы")
  • доступ values() С отражением в конструкторе не является оптимальным (особенно при взгляде на производительность), но два других варианта также имеют свои недостатки:
    • реализация как EnumSet.getUniverse() использует sun.misc класс
    • предоставление массива значений в качестве параметра имеет риск того, что заданные значения не являются правильными одни
  • поддерживаются только перечисления со значениями до 64 (это действительно недостаток?)
    • вы могли бы использовать BigInteger вместо
  • это не простой в использовании элементы поля в критерии запроса или на JPQL
    • вы можете использовать двоичные операторы или столбец битовой маски с соответствующими функциями, если ваша база данных поддерживает это

коллекции в JPA относятся к отношениям "один ко многим" или "многие ко многим", и они могут содержать только другие сущности. Извините, но вам нужно будет обернуть эти перечисления в сущность. Если вы подумаете об этом, вам понадобится какое-то поле ID и внешний ключ для хранения этой информации в любом случае. То есть, если вы не делаете что-то сумасшедшее, как хранить список через запятую в строке (не делайте этого!).