Colección de mapas JPA de Enums

92

¿Hay alguna forma en JPA de mapear una colección de Enums dentro de la clase Entity? ¿O la única solución es envolver Enum con otra clase de dominio y usarla para mapear la colección?

@Entity
public class Person {
    public enum InterestsEnum {Books, Sport, etc...  }
    //@???
    Collection<InterestsEnum> interests;
}

Estoy usando la implementación de Hibernate JPA, pero, por supuesto, preferiría una solución independiente de la implementación.

Gennady Shumakher
fuente

Respuestas:

112

usando Hibernate puedes hacer

@CollectionOfElements(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;

fuente
141
En caso de que alguien lea esto ahora ... @CollectionOfElements ahora está en desuso, en su lugar use: @ElementCollection
2
Puede encontrar una muestra en la respuesta de esta pregunta: stackoverflow.com/q/3152787/363573
Stephan
1
Como mencionaste Hibernate, pensé que esta respuesta podría haber sido específica del proveedor, pero no creo que lo sea a menos que usar JoinTable cause problemas en otras implementaciones. Por lo que he visto, creo que debería usarse CollectionTable en su lugar. Eso es lo que usé en mi respuesta y funciona para mí (aunque sí, también estoy usando Hibernate en este momento).
spaaarky21
Sé que este es un hilo antiguo, pero estamos implementando el mismo tipo de cosas usando javax.persistence. Cuando agregamos: @ElementCollection (targetClass = Roles.class) @CollectionTable (name = "USER_ROLES", joinColumns = @ JoinColumn (name = "USER_ID")) @Column (name = "ROLE", nullable = false) @Enumerated ( EnumType.STRING) privado Establecer roles <Roles>; en nuestra tabla de usuarios, las cosas se desvanecen en todo el paquete del modelo. En nuestro objeto Usuario, incluso un error en el generador @Id ... @ GeneratedValue de la clave principal y el primer @OneToMany arrojan errores tontos en la compilación.
LinuxLars
Por lo que vale, los errores que veo son un error - issues.jboss.org/browse/JBIDE-16016
LinuxLars
65

El enlace en la respuesta de Andy es un gran punto de partida para mapear colecciones de objetos "que no son de entidad" en JPA 2, pero no es del todo completo cuando se trata de mapear enumeraciones. Esto es lo que se me ocurrió.

@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
fuente
4
Lamentablemente, algunos "administradores" decidieron eliminar esa respuesta sin dar una razón (sobre la par del curso aquí). Para la referencia es datanucleus.org/products/accessplatform_3_0/jpa/orm/…
DataNucleus
2
Todo lo que realmente necesita de esto es @ElementCollectiony Collection<InterestsEnum> interests; El resto es potencialmente útil pero innecesario. Por ejemplo, @Enumerated(EnumType.STRING)coloca cadenas legibles por humanos en su base de datos.
CorayThan
2
Estás en lo correcto - en este ejemplo, se podía confiar en la @Column's nameestá implícito. Solo quería aclarar lo que está implícito cuando se omite @Column. Y @Enumerated siempre se recomienda ya que ordinal es una cosa horrible para predeterminar. :)
spaaarky21
Creo que vale la pena mencionar que en realidad necesitas la tabla
person_interest
Tuve que agregar el parámetro joinColumn para que funcione@CollectionTable(name="person_interest", joinColumns = {@JoinColumn(name="person_id")})
Tiago
8

Pude lograr esto de esta manera simple:

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

Se requiere una carga ansiosa para evitar el error de inicialización de carga diferida, como se explica aquí .

megalucio
fuente
5

Estoy usando una ligera modificación de java.util.RegularEnumSet para tener un 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 clase es ahora la base para todos mis conjuntos de enumeración:

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

Y ese conjunto lo puedo usar en mi entidad:

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

Ventajas:

  • Trabajar con un conjunto de enumeración seguro y eficaz de tipos en su código (consulte java.util.EnumSetpara obtener una descripción)
  • El conjunto es solo una columna numérica en la base de datos
  • todo es JPA simple (sin tipos personalizados específicos del proveedor )
  • declaración fácil (y breve) de nuevos campos del mismo tipo, en comparación con las otras soluciones

Inconvenientes:

  • Duplicación de código ( RegularEnumSety PersistentEnumSetson casi iguales)
    • Puede envolver el resultado de EnumSet.noneOf(enumType)en su PersistenEnumSet, declarar AccessType.PROPERTYy proporcionar dos métodos de acceso que utilizan la reflexión para leer y escribir el elementscampo
  • Se necesita una clase de conjunto adicional para cada clase de enumeración que debe almacenarse en un conjunto persistente
    • Si su proveedor de persistencia apoya incrustables sin un constructor público, se podría añadir @Embeddablea PersistentEnumSety soltar la clase extra ( ... interests = new PersistentEnumSet<>(InterestsEnum.class);)
  • Debe usar un @AttributeOverride, como se indica en mi ejemplo, si tiene más de uno PersistentEnumSeten su entidad (de lo contrario, ambos se almacenarían en la misma columna "elementos")
  • El acceso de values()con reflejo en el constructor no es óptimo (especialmente cuando se mira el rendimiento), pero las otras dos opciones también tienen sus inconvenientes:
    • Una implementación como EnumSet.getUniverse()hace uso de una sun.miscclase
    • Proporcionar la matriz de valores como parámetro tiene el riesgo de que los valores dados no sean los correctos
  • Solo se admiten enumeraciones con hasta 64 valores (¿es eso realmente un inconveniente?)
    • Podrías usar BigInteger en su lugar
  • No es fácil usar el campo de elementos en una consulta de criterios o JPQL
    • Puede usar operadores binarios o una columna de máscara de bits con las funciones apropiadas, si su base de datos lo admite.
Tobias Liefke
fuente
4

tl; dr Una solución corta sería la siguiente:

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

La respuesta larga es que con estas anotaciones, JPA creará una tabla que contendrá la lista de InterestsEnum apuntando al identificador de clase principal (Person.class en este caso).

@ElementCollections especifica dónde JPA puede encontrar información sobre el Enum

@CollectionTable crea la tabla que mantiene la relación de Person a InterestsEnum

@Enumerated (EnumType.STRING) le dice a JPA que persista el Enum como String, podría ser EnumType.ORDINAL

miserablebr
fuente
En este caso, no puedo cambiar esta colección porque se guarda como un PersistenceSet que es inmutable.
Nicolazz92
Mi error. Podemos cambiar este set con un setter.
Nicolazz92
0

Las colecciones en JPA se refieren a relaciones de uno a muchos o de muchos a muchos y solo pueden contener otras entidades. Lo siento, pero tendrías que envolver esas enumeraciones en una entidad. Si lo piensa, necesitará algún tipo de campo de identificación y clave externa para almacenar esta información de todos modos. Eso es a menos que haga algo loco como almacenar una lista separada por comas en una cadena (¡no haga esto!).

cletus
fuente
8
Esto solo es válido para JPA 1.0. En JPA 2.0 puede usar la anotación @ElementCollection como se muestra arriba.
rustyx