Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to write a generic enum converter for JPA?

I wanted to write a Converter for JPA that stores any enum as UPPERCASE. Some enums we encounter do not follow yet the convention to use only Uppercase letters so until they are refactored I still store the future value.

What I got so far:

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

I want "Started" to be stored as "STARTED" and so on.

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

the converter currently looks like this:

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

what will not work is that I do not know what enumClass will be at runtime. And I could not figure out a way to pass this information to the converter in the @Converter annotation.

So is there a way to add parameters to the converter or cheat a bit? Or is there another way?

I'm using EclipseLink 2.4.2

Thanks!

like image 983
wemu Avatar asked May 09 '14 12:05

wemu


People also ask

Can enums be generic?

However, it is possible to use enums in generics. The MSDN article for Enum gives the following type definition for the class Enum . This definition can be used to get enum s working as generic types by constraining the generic type to those of Enum .

Can enums be generic Java?

Java enums will be enhanced with generics support and with the ability to add methods to individual items, a new JEP shows. Since both features can be delivered with the same code change, they are bundled together in the same JEP. The change only affects the Java compiler, and therefore no runtime changes are needed.

Can enum be converted to string Java?

There are two ways to convert an Enum to String in Java, first by using the name() method of Enum which is an implicit method and available to all Enum, and second by using toString() method.

What is the default enum value type in JPA?

We have an entity with an enum field - emailCommunicationStatus , and we want to set a default value for it using JPA annotations - 'UNKNOWN' . However, when we save the entity to the DB, the value of this field is null and not . For the boolean field - isLocked the correct default value ( false ) is saved.


2 Answers

Based on @scottb solution I made this, tested against hibernate 4.3: (no hibernate classes, should run on JPA just fine)

Interface enum must implement:

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

Base abstract converter:

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

You must create a converter class for each enum, I find it easier to create static class inside the enum: (jpa/hibernate could just provide the interface for the enum, oh well...)

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

And mapping example with annotation:

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

With some changes you can create a IntegerEnum interface and generify for that.

like image 189
ChRoNoN Avatar answered Sep 22 '22 12:09

ChRoNoN


What you need to do is write a generic base class and then extend that for each enum type you want to persist. Then use the extended type in the @Converter annotation:

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

where Foo is the enum you want to handle.

The alternative would be to define a custom annotation, patch the JPA provider to recognize this annotation. That way, you could examine the field type as you build the mapping information and feed the necessary enum type into a purely generic converter.

Related:

  • https://hibernate.atlassian.net/browse/HHH-8854
like image 32
Aaron Digulla Avatar answered Sep 24 '22 12:09

Aaron Digulla