Коллекция карт JPA Enums

93

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

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

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

Геннадий Шумахер
источник

Ответы:

112

используя Hibernate, вы можете сделать

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

источник
141
На случай, если кто-нибудь прочитает это сейчас ... @CollectionOfElements устарела, вместо этого используйте: @ElementCollection
2
Вы можете найти образец в ответе на этот вопрос: stackoverflow.com/q/3152787/363573
Стефан
1
Поскольку вы упомянули Hibernate, я подумал, что этот ответ, возможно, зависит от поставщика, но я не думаю, что это так, если использование JoinTable не вызывает проблем в других реализациях. Из того, что я видел, я считаю, что вместо этого следует использовать CollectionTable. Это то, что я использовал в своем ответе, и это работает для меня (хотя да, я также использую Hibernate прямо сейчас.)
spaaarky21
Я знаю, что это старый поток, но мы реализуем то же самое, используя javax.persistence. Когда мы добавляем: @ElementCollection (targetClass = Roles.class) @CollectionTable (name = "USER_ROLES", joinColumns = @ JoinColumn (name = "USER_ID")) @Column (name = "ROLE", nullable = false) @Enumerated ( EnumType.STRING) private Установить роли <Roles>; к нашей таблице User, во всем модельном пакете все идет не так. В нашем объекте User даже ошибка генератора @Id ... @ GeneratedValue первичного ключа и первый @OneToMany выдают глупые ошибки при сборке.
LinuxLars
Как бы то ни было - ошибки, которые я вижу, являются ошибкой - issues.jboss.org/browse/JBIDE-16016
LinuxLars
65

Ссылка в ответе Энди - отличная отправная точка для сопоставления коллекций «не-Entity» объектов в 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;
}
spaaarky21
источник
4
К сожалению, какой-то «админ» решил удалить этот ответ без объяснения причин (примерно так, как здесь). Для справки это datanucleus.org/products/accessplatform_3_0/jpa/orm/…
DataNucleus
2
Все, что вам действительно нужно от этого, - это @ElementCollectionи Collection<InterestsEnum> interests; остальное потенциально полезно, но не нужно. Например, @Enumerated(EnumType.STRING)помещает в вашу базу данных удобочитаемые строки.
CorayThan
2
Вы правильно - в этом примере, вы можете рассчитывать на @Column«s nameподразумевалось. Я просто хотел прояснить, что подразумевается, когда @Column опущен. И всегда рекомендуется использовать @Enumerated, поскольку порядковый номер - это ужасная вещь по умолчанию. :)
spaaarky21 08
Думаю, стоит упомянуть, что вам действительно нужна таблица
person_interest
Мне пришлось добавить параметр joinColumn, чтобы он заработал@CollectionTable(name="person_interest", joinColumns = {@JoinColumn(name="person_id")})
Тьяго
8

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

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

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

мегалусио
источник
5

Я использую небольшую модификацию java.util.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поля
  • Дополнительный класс набора необходим для каждого класса перечисления, который должен храниться в постоянном наборе
    • Если ваш провайдер поддерживает сохранение embeddables без публичного конструктора, можно добавить @Embeddableк PersistentEnumSetи понизить дополнительный класс ( ... interests = new PersistentEnumSet<>(InterestsEnum.class);)
  • Вы должны использовать @AttributeOverride, как указано в моем примере, если у вас более одногоPersistentEnumSet в вашей сущности (иначе оба будут сохранены в одном столбце "elements")
  • Доступ values()с отражением в конструкторе не является оптимальным (особенно если смотреть на производительность), но два других варианта также имеют свои недостатки:
    • Такая реализация, как EnumSet.getUniverse()использование sun.miscкласса
    • Предоставление массива значений в качестве параметра может привести к тому, что указанные значения не верны.
  • Поддерживаются только перечисления до 64 значений (действительно ли это недостаток?)
    • Вместо этого вы можете использовать BigInteger
  • Нелегко использовать поле elements в запросе критериев или JPQL
    • Вы можете использовать бинарные операторы или столбец битовой маски с соответствующими функциями, если ваша база данных поддерживает это.
Тобиас Лифке
источник
4

tl; dr Краткое решение будет следующим:

@ElementCollection(targetClass = InterestsEnum.class)
@CollectionTable
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;

Длинный ответ заключается в том, что с этими аннотациями JPA создаст одну таблицу, которая будет содержать список InterestsEnum, указывающий на идентификатор основного класса (в данном случае Person.class).

@ElementCollections указывает, где JPA может найти информацию о Enum

@CollectionTable создает таблицу, в которой хранятся отношения от Person до InterestsEnum

@Enumerated (EnumType.STRING) сообщает JPA сохранять Enum как String, может быть EnumType.ORDINAL

mizerablebr
источник
В этом случае я не могу изменить эту коллекцию, потому что она сохраняется как неизменяемый PersistenceSet.
Nicolazz92
Виноват. Мы можем изменить этот набор с помощью сеттера.
Nicolazz92
0

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

Cletus
источник
8
Это действительно только для JPA 1.0. В JPA 2.0 вы можете использовать аннотацию @ElementCollection, как показано выше.
rustyx