Colección de mapas JPA de Enums
Pregunta
¿Hay alguna forma en JPA de asignar una colección de enumeraciones dentro de la clase Entidad?¿O la única solución es empaquetar 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.
Solución
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;
Otros consejos
El enlace en la respuesta de Andy es un excelente punto de partida para mapear colecciones de " non-Entity " objetos en JPA 2, pero no está completamente completo cuando se trata de enumeraciones de mapeo. 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;
}
Pude lograr esto de esta manera simple:
@ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;
Se requiere una carga ansiosa para evitar un error de inicialización de carga diferida como se explica aquí .
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 enumeraciones:
@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 una enumeración de tipo segura y de alto rendimiento establecida en su código (consulte
java.util.EnumSet
para una descripción) - El conjunto es solo una columna numérica en la base de datos.
- todo es JPA simple (sin proveedor específico tipos personalizados)
- declaración fácil (y breve) de nuevos campos del mismo tipo, en comparación con las otras soluciones
Desventajas:
- Duplicación de código (
RegularEnumSet
yPersistentEnumSet
son casi iguales)- Podrías envolver el resultado de
EnumSet.noneOf(enumType)
en tusPersistenEnumSet
, declararAccessType.PROPERTY
y proporciona dos métodos de acceso que utilizan la reflexión para leer y escribir elelements
campo
- Podrías envolver el resultado de
- 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 admite integrables sin un constructor público, puede agregar
@Embeddable
aPersistentEnumSet
y deja caer la clase extra (... interests = new PersistentEnumSet<>(InterestsEnum.class);
)
- Si su proveedor de persistencia admite integrables sin un constructor público, puede agregar
- Debes utilizar un
@AttributeOverride
, como se muestra en mi ejemplo, si tienes más de unoPersistentEnumSet
en su entidad (de lo contrario, ambos se almacenarían en la misma columna "elementos") - el acceso de
values()
con reflexión en el constructor no es óptimo (especialmente cuando se observa el rendimiento), pero las otras dos opciones también tienen sus inconvenientes:- Una implementación como
EnumSet.getUniverse()
hace uso de unsun.misc
clase - Proporcionar la matriz de valores como parámetro tiene el riesgo de que los valores dados no sean los correctos.
- Una implementación como
- Sólo se admiten enumeraciones con hasta 64 valores (¿es eso realmente un inconveniente?)
- Podrías usar BigInteger en su lugar
- No es fácil utilizar el campo elementos en una consulta de criterios o JPQL
- Puede utilizar 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 sentimos, pero necesitarías incluir esas enumeraciones en una entidad. Si lo piensa, necesitaría 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!).