Es posible escribir un genérico enum convertidor JPA?

Yo quería escribir un Convertidor de JPA que almacena cualquier enum como en MAYÚSCULAS. Algunas de las enumeraciones que nos encontramos no siga sin embargo, la convención para usar solamente letras Mayúsculas así hasta que se refactorizado yo aún almacenar el valor futuro.

Lo que tengo hasta el momento:

package student;

public enum StudentState {

    Started,
    Mentoring,
    Repeating,
    STUPID,
    GENIUS;
}

Quiero «comenzar» para ser almacenados como «INICIADO», 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 en la actualidad se parece a esto:

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 funciona es que no sé lo que enumClass será en tiempo de ejecución. Y yo no podía encontrar una manera de pasar esta información para el convertidor en el @Convertidor de anotación.

Por lo que hay una forma de agregar los parámetros del convertidor o hacer un poco de trampa? O hay otra manera?

Estoy usando EclipseLink 2.4.2

Gracias!

  • Ten en cuenta que esto es probable que sea frágil, especialmente porque es perfectamente legal que un enum para tener valores AVALUE y AValue.
  • sí eso es cierto, pero yo defino que como completamente prohibido 😀
InformationsquelleAutor wemu | 2014-05-09

3 Kommentare

  1. 17

    Lo que usted necesita hacer es escribir un genérico de la clase base y, a continuación, extender que para cada tipo enum desea persistir. A continuación, utilice los tipos extendidos en el @Converter anotación:

    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 se desea controlar.

    La alternativa sería definir una costumbre de anotación, el parche de la JPA proveedor de reconocer esta anotación. De esa manera, podría examinar el tipo de campo como de generar el mapeo de la información y de la alimentación de la necesaria tipo enum en un puramente conversor genérico.

    Relacionados con:

    • Pero, las enumeraciones no puede extender otra clase? stackoverflow.com/a/15450935/151845
    • Eso no importa en este caso. He añadido un ejemplo de cómo hacerlo.
    • Ahha! Pensé que estaba hablando de una clase abstracta de la Enumeración, no el Convertidor… esto se ve muy bien, y yo voy a dar una oportunidad. Gracias!
    • Esto funciona muy bien, gracias, con una salvedad: al menos en modo de Hibernación, deberá agregar implements AttributeConverter <Foo, String> a la declaración de su concreto FooConverter clase, gracias a este error: hibernate.atlassian.net/browse/HHH-8854
  2. 15

    Basa en @scottb solución que he hecho este, prueba en contra de hibernación 4.3: (no hibernate clases, se debe ejecutar en JPA muy bien)

    Interfaz enum debe implementar:

    public interface PersistableEnum<T> {
        public T getValue();
    }

    Base abstracta convertidor:

    @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();
        }
    }

    Debe crear un convertidor de clase para cada enum, creo que es más fácil crear la clase estática dentro de la enumeración: (jpa/hibernate sólo podría proporcionar la interfaz de la enumeración, oh, 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 la anotación:

    ...
    @Convert(converter = IndOrientation.Converter.class)
    private IndOrientation indOrientation;
    ...

    Con algunos cambios que usted puede crear un IntegerEnum interfaz y generify para que.

    • Hola Sergio, gracias por tu aporte. He implementado su solución y se retira @Convertir de AbstractEnumConverter porque era lanzar una excepción sobre ParameterizedType. Después de que todo funciona. Gracias.
    • Son bienvenidos. Yo tengo ese mismo enum implementado en mi código en el momento. Compruebe si usted no escriba los nombres incorrectos (@Convertir para el mapeado de atributo y @Convertidor para la clase).
    • Cuando se utiliza SpringData, usted necesita para eliminar la @Converter de la AbstractEnumConverter debido a la Primavera intentar automáticamente para registrar el convertidor en la configuración de Hibernate, que no funciona como el AbstractEnumConverter no tiene ningún defecto noarg constructor. De lo contrario, esto funciona como un encanto.
    • Solución agradable. He quitado @Converter anotación de AbstractEnumConverter y @Convert(converter = IndOrientation.Converter.class) de IndOrientation campo y en lugar de eso he añadido @Converter(autoApply = true) a la aplicación concreta Converter.
  3. 5

    Mi solución a este problema es similar y también hace uso de JPA 2.1 Convertidor de instalación. Por desgracia, los tipos genéricos en Java 8 no se concreta, y de modo que no parece ser una manera fácil de evitar escribir una clase separada para cada Java enumeración que usted quiere ser capaz de convertir de un formato de base de datos.

    Sin embargo, puede reducir el proceso de escritura de una enumeración convertidor de clase a pura repetitivo. Los componentes de esta solución son:

    1. Encodeable interfaz; el contrato para una clase enum que concede acceso a un String ficha por cada enum constante (también una fábrica para la obtención de la enumeración constante para una coincidencia token)
    2. AbstractEnumConverter clase; proporciona el código común para la traducción de fichas de madera/enum constantes
    3. Java enumeración de las clases que implementan la Encodeable interfaz
    4. JPA convertidor de clases que extienden el AbstractEnumConverter clase

    La Encodeable interfaz es simple y contiene una estático método de fábrica, forToken(), para la obtención de constantes enum:

    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 AbstractEnumConverter clase es una clase genérica que es también simple. Se implementa la JPA 2.1 AttributeConverter de la interfaz, pero no proporciona implementaciones para sus métodos (porque esta clase no puede saber los tipos concretos necesarios para la obtención de la correspondiente constantes enum). En su lugar, se define métodos auxiliares que el concreto convertidor de clases cadena:

    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);
        }
    }

    Un ejemplo de un hormigón clase enum que ahora podría ser persistente en una base de datos con JPA 2.1 Convertidor de instalación se muestra a continuación (nota que implementa Encodeable, y que el token para cada enum constante 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;
        }
    }

    Repetitivo para cada JPA 2.1 Convertidor de clase podría tener este aspecto (tenga en cuenta que cada convertidor se necesita extender AbstractEnumConverter y proporcionar implementaciones de los métodos de la JPA AttributeConverter interfaz):

    @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);
        }
    }

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

Pruebas en línea