java - enumerated - mapear enum jpa
Colección de mapas de JPA de Enums (5)
El enlace en la respuesta de Andy es un gran punto de partida para mapear colecciones de objetos "no-Entidad" en JPA 2, pero no está del todo completo cuando se trata de mapear enumeraciones. Esto es lo que se me ocurrió en su 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;
}
¿Hay alguna manera en JPA de mapear una colección de Enumerados dentro de la clase Entity? ¿O la única solución es envolver a 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 la solución agnóstica de implementación.
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 ahora es la base de todos mis conjuntos de enumeración:
@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
public InterestsSet() {
super(InterestsEnum.class);
}
}
Y ese conjunto que 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 una enumeración segura y de rendimiento configurada en su código (consulte
java.util.EnumSet
para obtener una descripción) - El conjunto es solo una columna numérica en la base de datos
- todo es JPA simple (no hay 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 (
RegularEnumSet
yPersistentEnumSet
son casi iguales)- Puede ajustar el resultado de
EnumSet.noneOf(enumType)
en suPersistenEnumSet
, declararAccessType.PROPERTY
y proporcionar dos métodos de acceso que utilizan el reflejo para leer y escribir el campo deelements
- Puede ajustar el resultado de
- Se necesita una clase set adicional para cada clase enum que debe almacenarse en un conjunto persistente
- Si su proveedor de persistencia admite incrustables sin un constructor público, puede agregar
@Embeddable
aPersistentEnumSet
y descartar la clase extra (... interests = new PersistentEnumSet<>(InterestsEnum.class);
)
- Si su proveedor de persistencia admite incrustables sin un constructor público, puede agregar
- Debe usar
@AttributeOverride
, como se indica en mi ejemplo, si tiene más de unPersistentEnumSet
en su entidad (de lo contrario, ambos se almacenarían en la misma columna "elements") - 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 clasesun.misc
- Proporcionar la matriz de valores como parámetro tiene el riesgo de que los valores dados no sean los correctos
- Una implementación como
- Solo se admiten las enumeraciones con hasta 64 valores (¿eso es realmente un inconveniente?)
- Podría 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
Las colecciones en JPA se refieren a relaciones uno a muchos o muchos a muchos y solo pueden contener otras entidades. Lo siento, pero tendrías que ajustar esas enumeraciones en una entidad. Si lo piensas bien, necesitarías algún tipo de campo de ID y clave foránea para almacenar esta información de todos modos. Eso es a menos que hagas algo loco como almacenar una lista separada por comas en una cadena (¡no hagas esto!).
Pude lograr esto de esta manera simple:
@ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;
Se requiere una carga ansiosa para evitar el error de inizialización de la carga diferida como se explica here .
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;