Коллекция карт JPA из Enums
Есть ли способ в JPA сопоставить коллекцию Enums внутри класса Entity? Или единственным решением является обернуть Enum другим классом домена и использовать его для сопоставления коллекции?
@Entity
public class Person {
public enum InterestsEnum {Books, Sport, etc... }
//@???
Collection<InterestsEnum> interests;
}
Я использую Hibernate JPA-реализацию, но, конечно, предпочла бы реализацию агностического решения.
Ответы
Ответ 1
используя Hibernate, вы можете сделать
@CollectionOfElements(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
Ответ 2
Ссылка в ответе Энди - отличная отправная точка для сопоставления наборов объектов "не-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;
}
Ответ 3
Я использую небольшую модификацию 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", [email protected](name="interests"))
private InterestsSet interests = new InterestsSet();
}
Преимущества:
- Работа с типом безопасного и исполняемого перечисления, установленного в вашем коде (см.
java.util.EnumSet
для описания)
- Набор представляет собой только один числовой столбец в базе данных
- все просто JPA (нестандартные пользовательские типы провайдера)
- легкое (и короткое) объявление новых полей того же типа по сравнению с другими решениями
Недостатки:
- Дублирование кода (
RegularEnumSet
и PersistentEnumSet
почти одинаковое)
- Вы можете обернуть результат
EnumSet.noneOf(enumType)
в PersistenEnumSet
, объявить AccessType.PROPERTY
и предоставить два метода доступа, которые используют отражение для чтения и записи поля elements
- Для каждого класса перечисления, который должен храниться в постоянном наборе, необходим дополнительный класс набора
- Если ваш провайдер persistence поддерживает встроенные вложения без публичного конструктора, вы можете добавить
@Embeddable
в PersistentEnumSet
и удалить
дополнительный класс (... interests = new PersistentEnumSet<>(InterestsEnum.class);
)
- Вы должны использовать
@AttributeOverride
, как указано в моем примере, если у вас есть более одного PersistentEnumSet
в вашей сущности (в противном случае оба будут сохранены в те же элементы столбца)
- Доступ
values()
с отражением в конструкторе не является оптимальным (особенно при просмотре производительности), но у двух других вариантов есть свои недостатки:
- Реализация, подобная
EnumSet.getUniverse()
, использует класс sun.misc
- Предоставление массива значений в качестве параметра имеет риск того, что заданные значения не являются правильными
- Поддерживаются только перечисления с до 64 значениями (это действительно недостаток?)
- Вместо этого вы можете использовать BigInteger
- Нелегко использовать поле элементов в запросе критериев или в JPQL
- Вы можете использовать бинарные операторы или битмакс-столбец с соответствующими функциями, если ваша база данных поддерживает это
Ответ 4
Коллекции в JPA относятся к отношениям один-ко-многим или многим ко многим, и они могут содержать только другие объекты. Извините, но вам нужно будет обернуть эти перечисления в сущности. Если вы подумаете об этом, вам понадобится какое-то поле идентификатора и внешний ключ, чтобы сохранить эту информацию в любом случае. То есть, если вы не делаете что-то сумасшедшее, как хранить список, разделенный запятыми, в String (не делайте этого!).
Ответ 5
Я смог выполнить это простым способом:
@ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;
Желательная загрузка необходима, чтобы избежать ленивой ошибки инициализации загрузки, как описано здесь.