Estoy buscando las diferentes formas de mapear una enumeración usando JPA. Especialmente quiero establecer el valor entero de cada entrada de enumeración y guardar solo el valor entero.
@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {
public enum Right {
READ(100), WRITE(200), EDITOR (300);
private int value;
Right(int value) { this.value = value; }
public int getValue() { return value; }
};
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
// the enum to map :
private Right right;
}
Una solución simple es usar la anotación enumerada con EnumType.ORDINAL:
@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;
Pero en este caso, JPA asigna el índice de enumeración (0,1,2) y no el valor que quiero (100,200,300).
Las dos soluciones que encontré no parecen simples ...
Primera solución
Una solución, propuesta aquí , utiliza @PrePersist y @PostLoad para convertir la enumeración a otro campo y marcar el campo de enumeración como transitorio:
@Basic
private int intValueForAnEnum;
@PrePersist
void populateDBFields() {
intValueForAnEnum = right.getValue();
}
@PostLoad
void populateTransientFields() {
right = Right.valueOf(intValueForAnEnum);
}
Segunda solución
La segunda solución propuesta aquí propuso un objeto de conversión genérico, pero aún parece pesado y orientado a la hibernación (@Type no parece existir en Java EE):
@Type(
type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
parameters = {
@Parameter(
name = "enumClass",
value = "Authority$Right"),
@Parameter(
name = "identifierMethod",
value = "toInt"),
@Parameter(
name = "valueOfMethod",
value = "fromInt")
}
)
¿Hay alguna otra solución?
Tengo varias ideas en mente pero no sé si existen en JPA:
- use los métodos setter y getter del miembro correcto de la clase de autoridad al cargar y guardar el objeto de autoridad
- una idea equivalente sería decirle a JPA cuáles son los métodos de Right enum para convertir enum a int e int a enum
- Debido a que estoy usando Spring, ¿hay alguna forma de decirle a JPA que use un convertidor específico (RightEditor)?
Respuestas:
Para versiones anteriores a JPA 2.1, JPA proporciona sólo dos maneras de lidiar con las enumeraciones, por su
name
o por suordinal
. Y el JPA estándar no admite tipos personalizados. Entonces:UserType
, EclipseLinkConverter
, etc.). (La segunda solución). ~ o ~int
valor ~ o ~Ilustraré la última opción (esta es una implementación básica, modifíquela según sea necesario):
fuente
Esto ahora es posible con JPA 2.1:
Más detalles:
fuente
@Converter
, ¡peroenum
debería manejarse de manera más elegante fuera de la caja!AttributeConverter
PERO cita algún código que no hace nada por el estilo y no responde al OP.AttributeConverter
@Convert(converter = Converter.class) @Enumerated(EnumType.STRING)
Desde JPA 2.1 puede usar AttributeConverter .
Cree una clase enumerada así:
Y crea un convertidor como este:
En la entidad solo necesitas:
@Converter(autoApply = true)
Su suerte puede variar según el contenedor, pero se probó que funciona en Wildfly 8.1.0. Si no funciona, puede agregar@Convert(converter = NodeTypeConverter.class)
en la columna de clase de entidad.fuente
El mejor enfoque sería mapear una ID única para cada tipo de enumeración, evitando así las trampas de ORDINAL y STRING. Vea esta publicación que describe 5 formas en que puede mapear una enumeración.
Tomado del enlace de arriba:
1 y 2. Usando @Enumerated
Actualmente hay 2 formas en que puede asignar enumeraciones dentro de sus entidades JPA utilizando la anotación @Enumerated. Desafortunadamente, tanto EnumType.STRING como EnumType.ORDINAL tienen sus limitaciones.
Si usa EnumType.String, cambiar el nombre de uno de sus tipos de enumeración hará que su valor de enumeración no esté sincronizado con los valores guardados en la base de datos. Si usa EnumType.ORDINAL, al eliminar o reordenar los tipos dentro de su enumeración, los valores guardados en la base de datos se correlacionarán con los tipos de enumeraciones incorrectos.
Ambas opciones son frágiles. Si se modifica la enumeración sin realizar una migración de la base de datos, podría poner en riesgo la integridad de sus datos.
3. Devolución del ciclo de vida
Una posible solución sería utilizar las anotaciones de devolución de llamadas del ciclo de vida de JPA, @PrePersist y @PostLoad. Esto se siente bastante feo ya que ahora tendrá dos variables en su entidad. Uno asigna el valor almacenado en la base de datos y el otro, la enumeración real.
4. Asignación de ID única a cada tipo de enumeración
La solución preferida es asignar su enumeración a un valor fijo, o ID, definido dentro de la enumeración. La asignación a un valor fijo predefinido hace que su código sea más robusto. Cualquier modificación al orden de los tipos de enumeraciones, o la refactorización de los nombres, no causará ningún efecto adverso.
5. Usando Java EE7 @Convert
Si está utilizando JPA 2.1, tiene la opción de usar la nueva anotación @Convert. Esto requiere la creación de una clase de convertidor, anotada con @Converter, dentro de la cual definiría qué valores se guardan en la base de datos para cada tipo de enumeración. Dentro de su entidad, anotaría su enumeración con @Convert.
Mi preferencia: (número 4)
La razón por la que prefiero definir mis ID dentro de la enumeración en lugar de usar un convertidor, es una buena encapsulación. Solo el tipo de enumeración debe conocer su ID, y solo la entidad debe saber cómo asigna la enumeración a la base de datos.
Ver la publicación original para el ejemplo de código.
fuente
Creo que el problema es que JPA nunca fue concebido con la idea en mente de que podríamos tener un Esquema preexistente complejo ya establecido.
Creo que hay dos defectos principales que resultan de esto, específicos de Enum:
Ayude a mi causa y vote sobre JPA_SPEC-47
¿No sería esto más elegante que usar un @Converter para resolver el problema?
fuente
Posiblemente el código relacionado cercano de Pascal
fuente
Yo haría lo siguiente:
Declare por separado la enumeración, en su propio archivo:
Declarar una nueva entidad JPA llamada Right
También necesitará un convertidor para recibir estos valores (solo JPA 2.1 y hay un problema que no discutiré aquí con estas enumeraciones para persistir directamente usando el convertidor, por lo que será un camino de un solo sentido)
La entidad Autoridad:
Al hacer esto, no puede establecer directamente en el campo enum. Sin embargo, puede establecer el campo Derecho en Autoridad usando
Y si necesita comparar, puede usar:
Lo cual es bastante bueno, creo. No es totalmente correcto, ya que el convertidor no está destinado a usarse con enum´s. En realidad, la documentación dice que nunca lo use para este propósito, debe usar la anotación @Enumerated en su lugar. El problema es que solo hay dos tipos de enumeración: ORDINAL o STRING, pero ORDINAL es complicado y no es seguro.
Sin embargo, si no te satisface, puedes hacer algo un poco más hacky y más simple (o no).
Veamos.
El RightEnum:
y la entidad Autoridad
En esta segunda idea, no es una situación perfecta ya que pirateamos el atributo ordinal, pero es una codificación mucho más pequeña.
Creo que la especificación JPA debe incluir el EnumType.ID donde el campo de valor de enumeración debe ser anotado con algún tipo de anotación @EnumId.
fuente
Mi propia solución para resolver este tipo de mapeo Enum JPA es la siguiente.
Paso 1 : escriba la siguiente interfaz que usaremos para todas las enumeraciones que queremos asignar a una columna db:
Paso 2 : implemente un convertidor JPA genérico personalizado de la siguiente manera:
Esta clase convertirá el valor enum
E
a un campo de tipo de base de datosT
(por ejemploString
) mediante el usogetDbVal()
de enumE
, y viceversa.Paso 3 : deje que la enumeración original implemente la interfaz que definimos en el paso 1:
Paso 4 : amplíe el convertidor del paso 2 para la
Right
enumeración del paso 3:Paso 5 : el paso final es anotar el campo en la entidad de la siguiente manera:
Conclusión
En mi humilde opinión, esta es la solución más limpia y elegante si tiene muchas enumeraciones para asignar y desea utilizar un campo particular de la enumeración como valor de asignación.
Para todas las otras enumeraciones en su proyecto que necesitan una lógica de mapeo similar, solo tiene que repetir los pasos 3 a 5, es decir:
IDbValue
en su enumeración;EnumDbValueConverter
con solo 3 líneas de código (también puede hacerlo dentro de su entidad para evitar crear una clase separada);@Convert
fromjavax.persistence
package.Espero que esto ayude.
fuente
primer caso (
EnumType.ORDINAL
)segundo caso (
EnumType.STRING
)fuente