2014-05-09 12 views
19

Volevo scrivere un convertitore per JPA che memorizza qualsiasi enumerazione come MAIUSCOLE. Alcune enumerazioni che incontriamo non seguono ancora la convenzione per usare solo lettere maiuscole così finché non vengono refactored, conservo ancora il valore futuro.È possibile scrivere un convertitore enum generico per JPA?

Quello che ho ottenuto finora:

package student; 

public enum StudentState { 

    Started, 
    Mentoring, 
    Repeating, 
    STUPID, 
    GENIUS; 
} 

che voglio "Started" per essere memorizzato come "Avviato" e così via.

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; 

} 

il convertitore attualmente appare così:

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

} 

ciò che non funziona è che io non so che cosa enumClass sarà in fase di esecuzione. E non sono riuscito a trovare un modo per passare queste informazioni al convertitore nell'annotazione @Converter.

Quindi c'è un modo per aggiungere parametri al convertitore o imbrogliare un po '? oppure c'è un'altro modo?

sto usando EclipseLink 2.4.2

Grazie!

+4

Attenzione che è probabile che sia fragile, soprattutto perché è perfettamente legale che un enum abbia valori "VALUE" e "VALORE". – chrylis

+0

sì è vero ma lo definisco assolutamente vietato: D – wemu

risposta

12

Quello che devi fare è scrivere una classe base generica e quindi estenderla per ogni tipo di enum che vuoi mantenere. Quindi utilizzare il tipo esteso nella @Converter della nota:

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

dove Foo è l'enum si desidera gestire.

L'alternativa sarebbe quella di definire un'annotazione personalizzata, applicare una patch al provider JPA per riconoscere questa annotazione. In questo modo, è possibile esaminare il tipo di campo durante la creazione delle informazioni di mappatura e immettere il tipo di enum necessario in un convertitore puramente generico.

correlati:

+0

Ma le enumerazioni non possono estendere un'altra classe? http://stackoverflow.com/a/15450935/151845 – pioto

+0

@pioto: non importa in questo caso.Ho aggiunto un esempio su come farlo. –

+0

Ahha! Pensavo che stavi parlando di una classe astratta per l'Enum, non il convertitore ... questo sembra fantastico, e farò un tentativo. Grazie! – pioto

4

La mia soluzione a questo problema è simile e si avvale anche della struttura JPA 2.1 Converter. Purtroppo, i tipi generici in Java 8 non sono reificati, e quindi non sembra essere un modo semplice per evitare di scrivere una classe separata per ogni enumerazione Java che si desidera essere in grado di convertire in/da un formato di database.

È tuttavia possibile ridurre il processo di scrittura di una classe di convertitore di enum in puro boilerplate.I componenti di questa soluzione sono:

  1. Encodeable interfaccia; il contratto per una classe enum che concede l'accesso a un token String per ogni costante enum (anche una factory per ottenere la costante enum per un token corrispondente)
  2. AbstractEnumConverter class; fornisce il codice comune per tradurre gettoni da/enum costanti
  3. Enumera classi Java che implementano l'interfaccia Encodeable
  4. classi convertitore JPA che estendono la classe AbstractEnumConverter

L'interfaccia Encodeable è semplice e contiene una fabbrica statico metodo, forToken(), per ottenere costanti 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 classe AbstractEnumConverter è un cl generico culo che è anche semplice. Implementa l'interfaccia AttributeConverter di JPA 2.1 ma non fornisce implementazioni per i suoi metodi (poiché questa classe non può conoscere i tipi concreti necessari per ottenere le costanti enum appropriate). Invece, definisce metodi di supporto che le classi convertitore concrete catena:

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 esempio di una classe enum concreto che potrebbe ora essere persistito a un database con l'impianto JPA 2.1 Converter è mostrato di seguito (si noti che implementa Encodeable, e che il token per ogni costante enum è definito come un campo privato):

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

il testo standard per ogni JPA 2.1 classe Converter sarebbe ora apparire così (si noti che ogni tale convertitore avrà bisogno di estendere AbstractEnumConverter e fornire implementazioni per i metodi dell'Attri JPA Interfaccia buteConverter):

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

Sulla base di soluzione @scottb ho fatto questo, testato contro ibernazione 4.3: (classi Hibernate, dovrebbe funzionare su APP bene)

enum interfaccia deve implementare:

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

Base converter Riepilogo:

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

è necessario creare una classe convertitore per ogni enum, trovo più facile creare classe statica all'interno della enum: (JPA/ibernazione potrebbe semplicemente fornire l'interfaccia per l'enumerazione, vabbè ...) esempio

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

e mappatura con annotazione:

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

Con alcune modifiche è possibile creare un'interfaccia IntegerEnum e generare per quello.

Problemi correlati