Coleção de mapas JPA de Enums

90

Existe uma maneira no JPA de mapear uma coleção de Enums dentro da classe Entity? Ou a única solução é envolver o 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;
}

Estou usando a implementação Hibernate JPA, mas é claro que preferiria uma solução independente de implementação.

Gennady Shumakher
fonte

Respostas:

112

usando o 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;

fonte
139
Caso alguém leia isso agora ... @CollectionOfElements agora está obsoleto, em vez disso use: @ElementCollection
2
Você pode encontrar um exemplo no respondente a esta pergunta: stackoverflow.com/q/3152787/363573
Stephan
1
Como você mencionou o Hibernate, pensei que essa resposta poderia ser específica do fornecedor, mas não acho que seja, a menos que o uso de JoinTable cause problemas em outras implementações. Pelo que tenho visto, acredito que CollectionTable deve ser usado em seu lugar. Foi isso que eu usei em minha resposta e funciona para mim (embora sim, eu também estou usando o Hibernate agora.)
spaaarky21
Eu sei que este é um tópico antigo, mas estamos implementando o mesmo tipo de coisa usando javax.persistence. Quando adicionamos: @ElementCollection (targetClass = Roles.class) @CollectionTable (name = "USER_ROLES", joinColumns = @ JoinColumn (name = "USER_ID")) @Column (name = "ROLE", nullable = false) @Enumerated ( EnumType.STRING) private Set <Roles> roles; para nossa tabela de usuário, as coisas se desintegram em todo o pacote do modelo. Em nosso objeto User, até mesmo um erro no gerador @Id ... @ GeneratedValue da chave primária e o primeiro @OneToMany lançam erros estúpidos na compilação.
LinuxLars
Pelo que vale a pena - os erros que estou vendo são um bug - issues.jboss.org/browse/JBIDE-16016
LinuxLars
65

O link na resposta de Andy é um excelente ponto de partida para mapear coleções de objetos "não Entidade" no JPA 2, mas não está totalmente completo quando se trata de mapear enums. Aqui está o que eu inventei.

@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
fonte
4
Infelizmente, algum "administrador" decidiu deletar essa resposta sem dar um motivo (quase o par para o curso aqui). Para a referência é datanucleus.org/products/accessplatform_3_0/jpa/orm/...
DataNucleus
2
Tudo o que você realmente precisa disso é @ElementCollectione Collection<InterestsEnum> interests; O resto é potencialmente útil, mas desnecessário. Por exemplo, @Enumerated(EnumType.STRING)coloca strings legíveis por humanos em seu banco de dados.
CorayThan
2
Você está correto - neste exemplo, você pode confiar que o @Columnestá nameimplícito. Eu só queria esclarecer o que está implícito quando @Column é omitido. E @Enumerated é sempre recomendado, já que ordinal é uma coisa horrível para se usar como padrão. :)
spaaarky21
Acho que vale a pena mencionar que você realmente precisa da tabela
person_interest
Tive de adicionar o parâmetro joinColumn para fazer funcionar@CollectionTable(name="person_interest", joinColumns = {@JoinColumn(name="person_id")})
Tiago
7

Consegui fazer isso desta maneira simples:

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

O carregamento rápido é necessário para evitar erros de inicialização de carregamento lento, conforme explicado aqui .

megalucio
fonte
5

Estou usando uma pequena modificação de java.util.RegularEnumSet para 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 conjuntos de enum:

@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
  public InterestsSet() {
    super(InterestsEnum.class);
  }
}

E esse conjunto posso usar na minha entidade:

@Entity
public class MyEntity {
  // ...
  @Embedded
  @AttributeOverride(name="elements", column=@Column(name="interests"))
  private InterestsSet interests = new InterestsSet();
}

Vantagens:

  • Trabalhar com um enum de tipo seguro e desempenho definido em seu código (consulte Recursos java.util.EnumSetpara obter uma descrição)
  • O conjunto é apenas uma coluna numérica no banco de dados
  • tudo é JPA simples (sem tipos personalizados específicos de provedor )
  • declaração fácil (e curta) de novos campos do mesmo tipo, em comparação com as outras soluções

Desvantagens:

  • Duplicação de código ( RegularEnumSete PersistentEnumSetsão quase iguais)
    • Você pode envolver o resultado de EnumSet.noneOf(enumType)em seu PersistenEnumSet, declarar AccessType.PROPERTYe fornecer dois métodos de acesso que usam reflexão para ler e escrever o elementscampo
  • Uma classe de conjunto adicional é necessária para cada classe de enum que deve ser armazenada em um conjunto persistente
    • Se o seu provedor de persistência suporta incorporáveis sem um construtor público, você pode adicionar @Embeddablea PersistentEnumSete solte a aula extra ( ... interests = new PersistentEnumSet<>(InterestsEnum.class);)
  • Você deve usar um @AttributeOverride, como mostrado no meu exemplo, se tiver mais de um PersistentEnumSetem sua entidade (caso contrário, ambos seriam armazenados na mesma coluna "elementos")
  • 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 também têm suas desvantagens:
    • Uma implementação como EnumSet.getUniverse()faz uso de uma sun.miscclasse
    • Fornecer a matriz de valores como parâmetro corre o risco de que os valores dados não sejam os corretos
  • Apenas enums com até 64 valores são suportados (isso é realmente uma desvantagem?)
    • Você poderia usar BigInteger em vez disso
  • Não é fácil usar o campo de elementos em uma consulta de critérios ou JPQL
    • Você pode usar operadores binários ou uma coluna de bitmask com as funções apropriadas, se o seu banco de dados suportar isso
Tobias Liefke
fonte
3

tl; dr Uma solução curta seria a seguinte:

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

A resposta longa é que com essas anotações JPA criará uma tabela que conterá a lista de InterestsEnum apontando para o identificador da classe principal (Person.class neste caso).

@ElementCollections especifica onde JPA pode encontrar informações sobre o Enum

@CollectionTable cria a tabela que mantém o relacionamento de Person para InterestsEnum

@Enumerated (EnumType.STRING) diz ao JPA para persistir o Enum como String, poderia ser EnumType.ORDINAL

mizerablebr
fonte
Nesse caso, não posso alterar essa coleção porque ela salva como um PersistenceSet, que é imutável.
Nicolazz92
Meu erro. Podemos mudar este conjunto com um setter.
Nicolazz92
0

As coleções na JPA referem-se a relacionamentos um para muitos ou muitos para muitos e podem conter apenas outras entidades. Desculpe, mas você precisa envolver esses enums em uma entidade. Se você pensar sobre isso, você precisaria de algum tipo de campo de ID e chave estrangeira para armazenar essas informações de qualquer maneira. A menos que você faça algo maluco como armazenar uma lista separada por vírgulas em uma String (não faça isso!).

cletus
fonte
8
Isso é válido apenas para JPA 1.0. No JPA 2.0, você pode usar a anotação @ElementCollection conforme mostrado acima.
enferrujado