É possível escrever um conversor de enum genérico para o JPA?

Eu queria escrever um conversor para JPA que armazena qualquer enum como UPPERCASE. Algumas enums que encontramos não seguem ainda a convenção de usar apenas letras maiúsculas para que elas sejam refatoradas e eu ainda armazene o valor futuro.

O que eu tenho até agora:

package student; public enum StudentState { Started, Mentoring, Repeating, STUPID, GENIUS; } 

Quero que “Started” seja armazenado como “STARTED” e assim por diante.

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

o conversor atualmente se parece com isso:

 package jpa; import javax.persistence.AttributeConverter; import java.util.EnumSet; public class EnumUppercaseConverter<E extends Enum> implements AttributeConverter { private Class 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; } } 

o que não vai funcionar é que eu não sei o que enumClass será em tempo de execução. E eu não consegui descobrir uma maneira de passar essa informação para o conversor na anotação @Converter.

Então, existe uma maneira de adicionar parâmetros ao conversor ou trapacear um pouco? Ou existe outra maneira?

Estou usando o EclipseLink 2.4.2

Obrigado!

O que você precisa fazer é escrever uma class base genérica e estendê-la para cada tipo de enum que deseja persistir. Em seguida, use o tipo estendido na anotação @Converter :

 public abstract class GenericEnumUppercaseConverter> implements AttributeConverter { ... } public FooConverter extends GenericEnumUppercaseConverter implements AttributeConverter // See Bug HHH-8854 { public FooConverter() { super(Foo.class); } } 

onde Foo é o enum que você quer manipular.

A alternativa seria definir uma anotação customizada, consertar o provedor JPA para reconhecer essa anotação. Dessa forma, você poderia examinar o tipo de campo à medida que cria as informações de mapeamento e alimenta o tipo de enumeração necessário em um conversor puramente genérico.

Relacionado:

Minha solução para esse problema parece semelhante e também faz uso do recurso JPA 2.1 Converter. Infelizmente, os tipos genéricos no Java 8 não são reificados e, portanto, não parece ser uma maneira fácil de evitar gravar uma class separada para cada enum Java que você deseja converter em / de um formato de database.

No entanto, você pode reduzir o processo de gravação de uma class do conversor de enum para clichê puro. Os componentes desta solução são:

  1. Interface Encodeable ; o contrato para uma class enum que concede access a um token String para cada constante enum (também uma fábrica para obter a constante enum para um token correspondente)
  2. Classe AbstractEnumConverter ; fornece o código comum para traduzir tokens de / para constantes de enum
  3. Classes de enum Java que implementam a interface Encodeable
  4. Classes de conversores JPA que estendem a class AbstractEnumConverter

A interface Encodeable é simples e contém um método de fábrica estático, forToken() , para obter constantes de enum:

 public interface Encodeable { String token(); public static  & Encodeable> E forToken(Class 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())); } } 

A class AbstractEnumConverter é uma class genérica que também é simples. Ele implementa a interface AttributeConverter do JPA 2.1, mas não fornece implementações para seus methods (porque essa class não pode conhecer os tipos concretos necessários para obter as constantes enum apropriadas). Em vez disso, define methods auxiliares aos quais as classs do conversor de concreto serão encadeadas para:

 public abstract class AbstractEnumConverter & Encodeable> implements AttributeConverter { public String toDatabaseColumn(E attr) { return (attr == null) ? null : attr.token(); } public E toEntityAttribute(Class cls, String dbCol) { return (dbCol == null) ? null : Encodeable.forToken(cls, dbCol); } } 

Um exemplo de uma class enum concreta que agora pode ser persistida em um database com o recurso Converter do JPA 2.1 é mostrado abaixo (observe que ele implementa Encodeable e que o token para cada constante de enumeração é definido como um 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; } } 

O clichê para cada class do Conversor JPA 2.1 agora ficaria assim (note que cada conversor precisará estender AbstractEnumConverter e fornecer implementações para os methods da interface JPA AttributeConverter):

 @Converter public class GenderCodeConverter extends AbstractEnumConverter { @Override public String convertToDatabaseColumn(GenderCode attribute) { return this.toDatabaseColumn(attribute); } @Override public GenderCode convertToEntityAttribute(String dbData) { return this.toEntityAttribute(GenderCode.class, dbData); } } 

Com base na solução @scottb eu fiz isso, testado contra o hibernate 4.3: (sem classs de hibernação, deve ser executado em JPA muito bem)

Interface enum deve implementar:

 public interface PersistableEnum { public T getValue(); } 

Conversor abstrato de base:

 @Converter public abstract class AbstractEnumConverter & PersistableEnum, E> implements AttributeConverter { private final Class clazz; public AbstractEnumConverter(Class 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(); } } 

Você deve criar uma class de conversor para cada enum, eu acho mais fácil criar class estática dentro do enum: (jpa / hibernate poderia apenas fornecer a interface para o enum, oh bem …)

 public enum IndOrientation implements PersistableEnum { 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 { public Converter() { super(IndOrientation.class); } } } 

E exemplo de mapeamento com anotação:

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

Com algumas alterações, você pode criar uma interface IntegerEnum e generificar para isso.