JPA карта коллекции Enums
Есть ли способ в JPA отобразить коллекцию Enums в классе Entity? Или единственное решение - обернуть Enum другим классом домена и использовать его для сопоставления коллекции?
@Entity
public class Person {
public enum InterestsEnum {Books, Sport, etc... }
//@???
Collection<InterestsEnum> interests;
}
Я использую реализацию Hibernate JPA, но, конечно, предпочел бы решение, не зависящее от реализации.
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;
}
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
Я смог сделать это простым способом:
@ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;
Стремительная загрузка требуется, чтобы избежать ленивой загрузки, приводящей к ошибке инициализации, как объяснено здесь.
Я использую небольшую модификацию 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
поле
- Вы можете обернуть результат
- Дополнительный класс набора необходим для каждого класса перечисления, который должен храниться в постоянном наборе
- Если ваш поставщик сохраняемости поддерживает встраиваемые файлы без открытого конструктора, вы можете добавить
@Embeddable
вPersistentEnumSet
и бросить лишний класс (... interests = new PersistentEnumSet<>(InterestsEnum.class);
)
- Если ваш поставщик сохраняемости поддерживает встраиваемые файлы без открытого конструктора, вы можете добавить
- Вы должны использовать
@AttributeOverride
как указано в моем примере, если у вас есть более одногоPersistentEnumSet
в вашей сущности (в противном случае оба будут храниться в одном столбце "элементы") - Доступ
values()
с отражением в конструкторе не является оптимальным (особенно если смотреть на производительность), но два других варианта также имеют свои недостатки:- Реализация как
EnumSet.getUniverse()
используетsun.misc
учебный класс - Предоставление массива значений в качестве параметра может привести к тому, что указанные значения будут неправильными
- Реализация как
- Поддерживаются только перечисления до 64 значений (это действительно недостаток?)
- Вы могли бы использовать BigInteger вместо
- Нелегко использовать поле элементов в запросе критериев или JPQL
- Вы можете использовать бинарные операторы или столбец битовой маски с соответствующими функциями, если ваша база данных поддерживает это
Коллекции в JPA относятся к отношениям "один ко многим" или "многие ко многим" и могут содержать только другие объекты. Извините, но вам нужно обернуть эти перечисления в сущности. Если вы подумаете об этом, вам понадобится какое-то поле идентификатора и внешний ключ для хранения этой информации в любом случае. Это если вы не делаете что-то сумасшедшее, например, храните разделенный запятыми список в строке (не делайте этого!).