Blog

Using ENUM’s with JPA but without the evil ordinal()

28 Aug, 2009
Xebia Background Header Wave

The ordinal of an Enum is used together with JPA to set the database value of an Enum type field of an entity. Since i find the use of the ordinal dangerous in case of future changes i was searching for an alternative way of populating my database field while still using the Enum in my application code.

The first obvious solution might be using the EnumType.STRING option of the @Enumerated annotation. In my opinion this could be usable when the database can be refactored to hold those string representations.
The case i ran into was a defined database field of NUMBER called status with the possible values of 0,100 and 200 meaning INITIAL, ACTIVE and INACTIVE. Using the default conversion with the below mentioned enum would mean that the values 0, 1 and 2 where used to insert into the database.

public enum Status {
  INITIAL, ACTIVE, INACTIVE;
}

and using the enum in the entity class

private Status status

And what if someone decides to add an extra status called CHECKED with a value of 10?
Should i create an Enum like this to be able to use the default?

public enum Status {
  INITIAL, UNUSED_1, …, UNUSED_9, CHECKED, UNUSED_11 .. UNUSED_99, ACTIVE, DO_YOU_GET_THE_FEELING?;
}

So i looked further and found that with EclipseLink (the JPA provider we use) it’s possible to define a Converter and use that in your Entities.  So let’s try.
First we need an Enum which contains the correct value inside: (See also a previous blog on Enums)

public enum Status {
  INITIAL(0), CHECKED(10), ACTIVE(100), INACTIVE(200);
  private final byte status;
  private Status(int value) {
    status = (byte) value;
  }
  public int getValue() {
    return status;
  }
}

Next we need a Converter:

public class StatusEnumConverter implements Converter {
  public Object convertDataValueToObjectValue(Object data, Session session) {
    //TODO implement converting database value to Enum
    return null;
  }
  public Object convertObjectValueToDataValue(Object data, Session session) {
    if (data instanceof Status) {
      return BigDecimal.valueOf(((Status) data).getValue());
    }
    return null;
  }
  public void initialize(DatabaseMapping dbMap, Session session) {
    // No need for database mapping
  }
  public boolean isMutable() {
    return false;
  }
}

That’s one way traffic now. How do we get form a value to the correct item of the enum?
Looking on the internet i found the Enum Inversion problem which supplied me with a solution for this. Also i wanted the StatusEnumConverter to be more generic so the converter could be easily reused. I create a ReverseEnumMap class to aid to the problem of getting the right Enum value and a convertable interface to generify (is that a word?) the Converter. The end result looks like this:

public class ReverseEnumMap<v extends Enum & Convertable> {
  private Map map = new HashMap();
  public ReverseEnumMap(Class valueType) {
    for (V v : valueType.getEnumConstants()) {
      map.put(v.convert(), v);
    }
  }
  public V get(Object obj) {
    return map.get(obj);
  }
}
public interface Convertable<e extends Enum & Convertable> {
  Object convert();
  E getFromValue(Object value);
}
public enum Status implements Convertable {
  INITIAL(0), CHECKED(10), ACTIVE(100), INACTIVE(200);
  private static ReverseEnumMap map = new ReverseEnumMap(Status.class);
  private final byte status;
  private Status(int value) {
    status = (byte) value;
  }
  public Object convert() {
    return BigDecimal.valueOf(status);
  }
  public Status getFromValue(Object obj) {
    return map.get(obj);
  }
}
public abstract class AbstractConverter implements Converter {
  public abstract Convertable getConvertableEnum();
  public Object convertDataValueToObjectValue(Object data, Session session) {
    if (data == null) {
      return getConvertableEnum();
    }
    Convertable convertableEnum = getConvertableEnum().getFromValue(data);
    if (convertableEnum == null) {
      throw new IllegalArgumentException(
          "Data not with a value suitable got [" + data.getClass() +" : "+data
          + "] expected a valid value of ["
          + getConvertableEnum().getClass() + "]");
    } else {
      return convertableEnum;
    }
  }
  public Object convertObjectValueToDataValue(Object data, Session session) {
    if (data == null) {
      return getConvertableEnum().convert();
    }
    if (data instanceof Convertable) {
      return ((Convertable) data).convert();
    }
    throw new IllegalArgumentException("Data not of correct type got ["
      + data.getClass() + "] expected [Convertable]");
  }
  //left out the other methods for readibility
}
public class StatusConverter extends AbstractConverter {
  private static final long serialVersionUID = 6209909216228257358L;
  @Override
  public Convertable getConvertableEnum() {
    return Status.INITIAL;
  }
}

And this is how you will use the converter in your entity class. Annotating the status field. Note that you only have to define the @Converter annotation once to be able to use the @Convert in other entities also!

@Converter(name="status", converterClass=com.xebia.enum.converters.StatusConverter.class)
@Convert("status")
private Status status;

So with this i think we have a pretty generic solution to avoid using ordinal with enums and JPA.

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts