Колекція карт JPA Enum


91

Чи існує спосіб у JPA зіставити колекцію Enums у класі 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. Це те, що я використав у своїй відповіді, і це працює для мене (хоча так, я також зараз використовую сплячий режим.)
spaaarky21

Я знаю, що це стара тема, але ми реалізуємо те саме, використовуючи javax.persistence. Коли ми додаємо: @ElementCollection (targetClass = Roles.class) @CollectionTable (name = "USER_ROLES", joinColumns = @ JoinColumn (name = "USER_ID")) @Column (name = "ROLE", nullable = false) @Enumerated ( EnumType.STRING) приватний набір ролей <Roles>; до нашої таблиці користувачів, речі спалахують у всьому пакеті моделей. У нашому об'єкті User навіть помилка у генераторі @Id ... @ GeneratedValue первинного ключа та перші помилки @OneToMany, що викидають туписті помилки при побудові.
LinuxLars

Чого варте - помилки, які я бачу, є помилкою - issues.jboss.org/browse/JBIDE-16016
LinuxLars

65

Посилання у відповіді Енді є чудовою відправною точкою для зіставлення колекцій "несуб'єктних" об'єктів у 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;
}

4
На жаль, якийсь "адміністратор" вирішив видалити цю відповідь, не вказавши причини (приблизно для курсу тут). Для довідки це datanucleus.org/products/accessplatform_3_0/jpa/orm/…
DataNucleus

2
Все, що вам насправді потрібно з цього, - це, @ElementCollectionа Collection<InterestsEnum> interests; інше - потенційно корисне, але непотрібне. Наприклад, @Enumerated(EnumType.STRING)поміщає зручні для читання рядки у вашу базу даних.
Coray, ніж

2
Ви маєте рацію - у цьому прикладі ви можете покладатися на те @Column, nameщо мається на увазі. Я просто хотів пояснити, що мається на увазі, коли @Column пропущено. І @Enumerated завжди рекомендується, оскільки порядковий номер - це жахлива річ, за замовчуванням. :)
spaaarky21

Думаю, варто зазначити, що вам насправді потрібна таблиця
person_interest

Мені довелося додати параметр joinColumn, щоб він працював@CollectionTable(name="person_interest", joinColumns = {@JoinColumn(name="person_id")})
Тіаго

7

Я зміг зробити це таким простим способом:

@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 (інакше обидва вони зберігатимуться в одному і тому ж стовпці "елементи")
  • Доступ до values()з відображенням у конструкторі не є оптимальним (особливо якщо дивитись на продуктивність), але два інших варіанти також мають свої недоліки:
    • Реалізація типу EnumSet.getUniverse()використовує sun.miscклас
    • Надання масиву значень як параметра має ризик того, що задані значення не є правильними
  • Підтримуються лише переліки із значеннями до 64 (це насправді недолік?)
    • Замість цього ви можете використовувати BigInteger
  • Використовувати поле елементів у запиті критеріїв або 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


У цьому випадку я не можу змінити цю колекцію, оскільки вона зберігається як PersistentSet, який є незмінним.
Nicolazz92

Моя помилка. Ми можемо змінити цей набір за допомогою сетера.
Nicolazz92

0

Колекції в JPA стосуються відносин "один до багатьох" або "багато до багатьох", і вони можуть містити лише інші сутності. Вибачте, але вам потрібно було б обернути ці переліки в сутності. Якщо ви задумаєтесь, вам все одно знадобиться якесь поле ідентифікатора та зовнішній ключ, щоб зберегти цю інформацію. Тобто, якщо ви не зробите чогось божевільного, наприклад, зберігаєте список, відокремлений комами, у рядку (не робіть цього!).


8
Це справедливо лише для JPA 1.0. У JPA 2.0 ви можете використовувати анотацію @ElementCollection, як показано вище.
rustyx
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.