JPA mapa coleção de enumerações
Pergunta
Existe uma maneira em JPA para mapear uma coleção de enumerações dentro da classe Entity? Ou a única solução é envolver Enum com outra classe de domínio e usá-lo para mapear a coleção?
@Entity
public class Person {
public enum InterestsEnum {Books, Sport, etc... }
//@???
Collection<InterestsEnum> interests;
}
Eu estou usando implementação Hibernate JPA, mas é claro que preferiria implementação da solução agnóstica.
Solução
usando Hibernate você pode fazer
@CollectionOfElements(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
Outras dicas
O link na resposta de Andy é um excelente ponto de partida para as coleções de mapeamento de "não-entidade" objetos em JPA 2, mas não é bem completo quando se trata de enums mapeamento. Aqui está o que eu vim acima com em seu lugar.
@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;
}
Eu era capaz de fazer isso desta forma simples:
@ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;
Carga antecipada é necessária a fim de evitar o carregamento lento inizializing erro como explicado aqui .
Eu estou usando uma ligeira modificação do java.util.RegularEnumSet ter um EnumSet persistente:
@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
// ...
}
Esta classe é agora a base para todos os meus sets enum:
@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
public InterestsSet() {
super(InterestsEnum.class);
}
}
E esse conjunto que posso usar na minha entidade:
@Entity
public class MyEntity {
// ...
@Embedded
@AttributeOverride(name="elements", column=@Column(name="interests"))
private InterestsSet interests = new InterestsSet();
}
Vantagens:
- Trabalho com um tipo de seguro e conjunto enum performance em seu código (ver
java.util.EnumSet
para uma descrição) - O conjunto é apenas uma coluna numérica no banco de dados
- tudo é JPA simples (sem fornecedor específico tipos personalizados )
- fácil (e curta) declaração de novos campos do mesmo tipo, em comparação com as outras soluções
Desvantagens:
- duplicação de código (
RegularEnumSet
ePersistentEnumSet
são quase o mesmo)- Você poderia envolver o resultado de
EnumSet.noneOf(enumType)
em suaPersistenEnumSet
,AccessType.PROPERTY
declarar e fornecer dois métodos de acesso que a reflexão usar para ler e escrever o campoelements
- Você poderia envolver o resultado de
- Uma classe de conjunto adicional é necessário para todas as classes de enumeração que deve ser armazenado em um conjunto persistente
- Se o provedor de persistência suporta incorporáveis ??sem um construtor público, você pode adicionar
@Embeddable
paraPersistentEnumSet
e soltar o aula extra (... interests = new PersistentEnumSet<>(InterestsEnum.class);
)
- Se o provedor de persistência suporta incorporáveis ??sem um construtor público, você pode adicionar
- Você deve usar um
@AttributeOverride
, como dado no meu exemplo, se você tem mais de umPersistentEnumSet
na sua entidade (caso contrário, ambos seriam armazenados com os mesmos "elementos" da coluna) - O acesso de
values()
com reflexão no construtor não é o ideal (especialmente quando se olha para o desempenho), mas as outras duas opções têm suas desvantagens também:- Uma implementação como
EnumSet.getUniverse()
faz uso de uma classesun.misc
- Fornecer a matriz valores como parâmetro tem o risco de que os valores apresentados não são os corretos
- Uma implementação como
- Somente enums com até 64 valores são suportados (isso é realmente uma desvantagem?)
- Você pode usar BigInteger vez
- Não é fácil usar o campo de elementos em uma consulta critérios ou JPQL
- Você pode usar operadores binários ou uma coluna máscara de bits com as funções apropriadas, se o seu banco de dados suporta que
Coleções em JPA referem-se a um-para-muitos ou muitos-para-muitos relacionamentos e eles só podem conter outras entidades. Desculpe, mas você precisa quebrar esses enums em uma entidade. Se você pensar sobre isso, você precisa de algum tipo de campo ID e chave estrangeira para armazenar essas informações de qualquer maneira. Isso é menos que você faça algo louco como armazenar uma lista separada por vírgula em uma String (não faça isso!).