java - manager - no se puede encontrar una unidad de persistencia nombrada
¿Es posible escribir un convertidor de enumeración genérico para JPA? (3)
Basándome en la solución @scottb que hice, probé contra hibernate 4.3: (no hay clases de hibernate, debería funcionar en JPA muy bien)
La enumeración de la interfaz debe implementar:
public interface PersistableEnum<T> {
public T getValue();
}
Base convertidor abstracto:
@Converter
public abstract class AbstractEnumConverter<T extends Enum<T> & PersistableEnum<E>, E> implements AttributeConverter<T, E> {
private final Class<T> clazz;
public AbstractEnumConverter(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public E convertToDatabaseColumn(T attribute) {
return attribute != null ? attribute.getValue() : null;
}
@Override
public T convertToEntityAttribute(E dbData) {
T[] enums = clazz.getEnumConstants();
for (T e : enums) {
if (e.getValue().equals(dbData)) {
return e;
}
}
throw new UnsupportedOperationException();
}
}
Debes crear una clase de conversión para cada enumeración, me resulta más fácil crear una clase estática dentro de la enumeración: (jpa / hibernate podría proporcionar la interfaz para la enumeración, bueno ...)
public enum IndOrientation implements PersistableEnum<String> {
LANDSCAPE("L"), PORTRAIT("P");
private final String value;
@Override
public String getValue() {
return value;
}
private IndOrientation(String value) {
this.value= value;
}
public static class Converter extends AbstractEnumConverter<IndOrientation, String> {
public Converter() {
super(IndOrientation.class);
}
}
}
Y ejemplo de mapeo con anotación:
...
@Convert(converter = IndOrientation.Converter.class)
private IndOrientation indOrientation;
...
Con algunos cambios puede crear una interfaz IntegerEnum y generar para eso.
Quería escribir un convertidor para JPA que almacene cualquier enumeración como MAYÚSCULAS. Algunas enumeraciones con las que nos encontramos todavía no siguen la convención de usar solo letras mayúsculas, por lo que, hasta que sean refactorizadas, sigo almacenando el valor futuro.
Lo que tengo hasta ahora:
package student;
public enum StudentState {
Started,
Mentoring,
Repeating,
STUPID,
GENIUS;
}
Quiero que "Started" se almacene como "STARTED" y así sucesivamente.
package student;
import jpa.EnumUppercaseConverter;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name = "STUDENTS")
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long mId;
@Column(name = "LAST_NAME", length = 35)
private String mLastName;
@Column(name = "FIRST_NAME", nullable = false, length = 35)
private String mFirstName;
@Column(name = "BIRTH_DATE", nullable = false)
@Temporal(TemporalType.DATE)
private Date mBirthDate;
@Column(name = "STUDENT_STATE")
@Enumerated(EnumType.STRING)
@Convert(converter = EnumUppercaseConverter.class)
private StudentState studentState;
}
El convertidor actualmente se ve así:
package jpa;
import javax.persistence.AttributeConverter;
import java.util.EnumSet;
public class EnumUppercaseConverter<E extends Enum<E>> implements AttributeConverter<E, String> {
private Class<E> enumClass;
@Override
public String convertToDatabaseColumn(E e) {
return e.name().toUpperCase();
}
@Override
public E convertToEntityAttribute(String s) {
// which enum is it?
for (E en : EnumSet.allOf(enumClass)) {
if (en.name().equalsIgnoreCase(s)) {
return en;
}
}
return null;
}
}
lo que no funcionará es que no sé qué clase de enumeración será en tiempo de ejecución. Y no pude encontrar una manera de pasar esta información al convertidor en la anotación @Converter.
Entonces, ¿hay una manera de agregar parámetros al convertidor o hacer un poco de trampa? ¿O hay otra manera?
Estoy usando EclipseLink 2.4.2
¡Gracias!
Lo que debe hacer es escribir una clase base genérica y luego extenderla para cada tipo de enumeración que desee conservar. Luego use el tipo extendido en la anotación @Converter
:
public abstract class GenericEnumUppercaseConverter<E extends Enum<E>> implements AttributeConverter<E, String> {
...
}
public FooConverter
extends GenericEnumUppercaseConverter<Foo>
implements AttributeConverter<Foo, String> // See Bug HHH-8854
{
public FooConverter() {
super(Foo.class);
}
}
donde Foo
es la enumeración que quieres manejar.
La alternativa sería definir una anotación personalizada, parchear el proveedor de JPA para que reconozca esta anotación. De esa manera, podría examinar el tipo de campo a medida que construye la información de mapeo y alimenta el tipo de enumeración necesario en un convertidor puramente genérico.
Relacionado:
Mi solución a este problema parece similar y también hace uso de la facilidad JPA 2.1 Converter. Lamentablemente, los tipos genéricos en Java 8 no se reifican, por lo que no parece haber una manera fácil de evitar escribir una clase separada para cada enumeración de Java que desea convertir a / desde un formato de base de datos.
Sin embargo, puede reducir el proceso de escritura de una clase de convertidor de enumeración a repetición pura. Los componentes de esta solución son:
- Interfaz
Encodeable
; el contrato para una clase de enumeración que otorga acceso a un token deString
para cada constante de enumeración (también una fábrica para obtener la constante de enumeración para un token coincidente) - Clase
AbstractEnumConverter
; proporciona el código común para traducir tokens a / desde constantes de enumeración - Clases de enumeración de Java que implementan la interfaz
Encodeable
- Clases convertidor JPA que extienden la clase
AbstractEnumConverter
La interfaz Encodeable
es simple y contiene un método de fábrica estático, forToken()
, para obtener constantes de enumeración:
public interface Encodeable {
String token();
public static <E extends Enum<E> & Encodeable> E forToken(Class<E> cls, String tok) {
final String t = tok.trim().toUpperCase();
return Stream.of(cls.getEnumConstants())
.filter(e -> e.token().equals(t))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown token ''" +
tok + "'' for enum " + cls.getName()));
}
}
La clase AbstractEnumConverter
es una clase genérica que también es simple. Implementa la interfaz AttributeConverter de JPA 2.1, pero no proporciona implementaciones para sus métodos (porque esta clase no puede conocer los tipos concretos necesarios para obtener las constantes de enumeración adecuadas). En su lugar, define los métodos de ayuda que las clases de convertidor de concreto encadenarán a
public abstract class AbstractEnumConverter<E extends Enum<E> & Encodeable>
implements AttributeConverter<E, String> {
public String toDatabaseColumn(E attr) {
return (attr == null)
? null
: attr.token();
}
public E toEntityAttribute(Class<E> cls, String dbCol) {
return (dbCol == null)
? null
: Encodeable.forToken(cls, dbCol);
}
}
A continuación se muestra un ejemplo de una clase de enumeración concreta que ahora podría persistir en una base de datos con la facilidad JPA 2.1 Converter (tenga en cuenta que implementa Encodeable
y que el token para cada constante de enumeración se define como un campo privado):
public enum GenderCode implements Encodeable {
MALE ("M"),
FEMALE ("F"),
OTHER ("O");
final String e_token;
GenderCode(String v) {
this.e_token = v;
}
@Override
public String token() {
return this.e_token;
}
}
La placa de repetición para cada clase de Convertidor JPA 2.1 ahora se vería así (tenga en cuenta que cada convertidor tendrá que extender AbstractEnumConverter
y proporcionar implementaciones para los métodos de la interfaz AttributeConverter de JPA):
@Converter
public class GenderCodeConverter
extends AbstractEnumConverter<GenderCode> {
@Override
public String convertToDatabaseColumn(GenderCode attribute) {
return this.toDatabaseColumn(attribute);
}
@Override
public GenderCode convertToEntityAttribute(String dbData) {
return this.toEntityAttribute(GenderCode.class, dbData);
}
}