Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map enum in JPA with fixed values?

People also ask

Can enum values be persisted?

ORDINAL) annotation on the enum field, JPA will use the Enum. ordinal() value when persisting a given entity in the database. A problem arises with this kind of mapping when we need to modify our enum. If we add a new value in the middle or rearrange the enum's order, we'll break the existing data model.

How do I map an enum to a column in a database?

To map the Enum to a String database column type, you need to specify the EnumType. STRING value when using the @Enumerated annotation. As expected, the String representation of the Java Enum was used to populate the associated database column value.

How do you persist enum?

If you instead want to persist the String representation of your enum value, you need to annotate your entity attribute with @Enumerated(EnumType. STRING). When you take a look at the database, you can see that Hibernate now persisted the name of the enum value.

What annotation is used if enum is used in an entity?

In our Movie entity, we add a rating field of the enum type Rating and annotate it with @Enumerated(EnumType. STRING) to declare that its value should be converted from what is effectively a String in the database to the Rating type.


For versions earlier than JPA 2.1, JPA provides only two ways to deal with enums, by their name or by their ordinal. And the standard JPA doesn't support custom types. So:

  • If you want to do custom type conversions, you'll have to use a provider extension (with Hibernate UserType, EclipseLink Converter, etc). (the second solution). ~or~
  • You'll have to use the @PrePersist and @PostLoad trick (the first solution). ~or~
  • Annotate getter and setter taking and returning the int value ~or~
  • Use an integer attribute at the entity level and perform a translation in getters and setters.

I'll illustrate the latest option (this is a basic implementation, tweak it as required):

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

    public enum Right {
        READ(100), WRITE(200), EDITOR (300);

        private int value;

        Right(int value) { this.value = value; }    

        public int getValue() { return value; }

        public static Right parse(int id) {
            Right right = null; // Default
            for (Right item : Right.values()) {
                if (item.getValue()==id) {
                    right = item;
                    break;
                }
            }
            return right;
        }

    };

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "AUTHORITY_ID")
    private Long id;

    @Column(name = "RIGHT_ID")
    private int rightId;

    public Right getRight () {
        return Right.parse(this.rightId);
    }

    public void setRight(Right right) {
        this.rightId = right.getValue();
    }

}

This is now possible with JPA 2.1:

@Column(name = "RIGHT")
@Enumerated(EnumType.STRING)
private Right right;

Further details:

  • https://dzone.com/articles/mapping-enums-done-right
  • http://www.thoughts-on-java.org/jpa-21-how-to-implement-type-converter/

From JPA 2.1 you can use AttributeConverter.

Create an enumerated class like so:

public enum NodeType {

    ROOT("root-node"),
    BRANCH("branch-node"),
    LEAF("leaf-node");

    private final String code;

    private NodeType(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

And create a converter like this:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class NodeTypeConverter implements AttributeConverter<NodeType, String> {

    @Override
    public String convertToDatabaseColumn(NodeType nodeType) {
        return nodeType.getCode();
    }

    @Override
    public NodeType convertToEntityAttribute(String dbData) {
        for (NodeType nodeType : NodeType.values()) {
            if (nodeType.getCode().equals(dbData)) {
                return nodeType;
            }
        }

        throw new IllegalArgumentException("Unknown database value:" + dbData);
    }
}

On the entity you just need:

@Column(name = "node_type_code")

You luck with @Converter(autoApply = true) may vary by container but tested to work on Wildfly 8.1.0. If it doesn't work you can add @Convert(converter = NodeTypeConverter.class) on the entity class column.


The best approach would be to map a unique ID to each enum type, thus avoiding the pitfalls of ORDINAL and STRING. See this post which outlines 5 ways you can map an enum.

Taken from the link above:

1&2. Using @Enumerated

There are currently 2 ways you can map enums within your JPA entities using the @Enumerated annotation. Unfortunately both EnumType.STRING and EnumType.ORDINAL have their limitations.

If you use EnumType.String then renaming one of your enum types will cause your enum value to be out of sync with the values saved in the database. If you use EnumType.ORDINAL then deleting or reordering the types within your enum will cause the values saved in the database to map to the wrong enums types.

Both of these options are fragile. If the enum is modified without performing a database migration, you could jeopodise the integrity of your data.

3. Lifecycle Callbacks

A possible solution would to use the JPA lifecycle call back annotations, @PrePersist and @PostLoad. This feels quite ugly as you will now have two variables in your entity. One mapping the value stored in the database, and the other, the actual enum.

4. Mapping unique ID to each enum type

The preferred solution is to map your enum to a fixed value, or ID, defined within the enum. Mapping to predefined, fixed value makes your code more robust. Any modification to the order of the enums types, or the refactoring of the names, will not cause any adverse effects.

5. Using Java EE7 @Convert

If you are using JPA 2.1 you have the option to use the new @Convert annotation. This requires the creation of a converter class, annotated with @Converter, inside which you would define what values are saved into the database for each enum type. Within your entity you would then annotate your enum with @Convert.

My preference: (Number 4)

The reason why I prefer to define my ID's within the enum as oppose to using a converter, is good encapsulation. Only the enum type should know of its ID, and only the entity should know about how it maps the enum to the database.

See the original post for the code example.


The problem is, I think, that JPA was never incepted with the idea in mind that we could have a complex preexisting Schema already in place.

I think there are two main shortcomings resulting from this, specific to Enum:

  1. The limitation of using name() and ordinal(). Why not just mark a getter with @Id, the way we do with @Entity?
  2. Enum's have usually representation in the database to allow association with all sorts of metadata, including a proper name, a descriptive name, maybe something with localization etc. We need the easy of use of an Enum combined with the flexibility of an Entity.

Help my cause and vote on JPA_SPEC-47

Would this not be more elegant than using a @Converter to solve the problem?

// Note: this code won't work!!
// it is just a sample of how I *would* want it to work!
@Enumerated
public enum Language {
  ENGLISH_US("en-US"),
  ENGLISH_BRITISH("en-BR"),
  FRENCH("fr"),
  FRENCH_CANADIAN("fr-CA");
  @ID
  private String code;
  @Column(name="DESCRIPTION")
  private String description;

  Language(String code) {
    this.code = code;
  }

  public String getCode() {
    return code;
  }

  public String getDescription() {
    return description;
  }
}

My own solution to solve this kind of Enum JPA mapping is the following.

Step 1 - Write the following interface that we will use for all enums that we want to map to a db column:

public interface IDbValue<T extends java.io.Serializable> {

    T getDbVal();

}

Step 2 - Implement a custom generic JPA converter as follows:

import javax.persistence.AttributeConverter;

public abstract class EnumDbValueConverter<T extends java.io.Serializable, E extends Enum<E> & IDbValue<T>>
        implements AttributeConverter<E, T> {

    private final Class<E> clazz;

    public EnumDbValueConverter(Class<E> clazz){
        this.clazz = clazz;
    }

    @Override
    public T convertToDatabaseColumn(E attribute) {
        if (attribute == null) {
            return null;
        }
        return attribute.getDbVal();
    }

    @Override
    public E convertToEntityAttribute(T dbData) {
        if (dbData == null) {
            return null;
        }
        for (E e : clazz.getEnumConstants()) {
            if (dbData.equals(e.getDbVal())) {
                return e;
            }
        }
        // handle error as you prefer, for example, using slf4j:
        // log.error("Unable to convert {} to enum {}.", dbData, clazz.getCanonicalName());
        return null;
    }

}

This class will convert the enum value E to a database field of type T (e.g. String) by using the getDbVal() on enum E, and vice versa.

Step 3 - Let the original enum implement the interface we defined in step 1:

public enum Right implements IDbValue<Integer> {
    READ(100), WRITE(200), EDITOR (300);

    private final Integer dbVal;

    private Right(Integer dbVal) {
        this.dbVal = dbVal;
    }

    @Override
    public Integer getDbVal() {
        return dbVal;
    }
}

Step 4 - Extend the converter of step 2 for the Right enum of step 3:

public class RightConverter extends EnumDbValueConverter<Integer, Right> {
    public RightConverter() {
        super(Right.class);
    }
}

Step 5 - The final step is to annotate the field in the entity as follows:

@Column(name = "RIGHT")
@Convert(converter = RightConverter.class)
private Right right;

Conclusion

IMHO this is the cleanest and most elegant solution if you have many enums to map and you want to use a particular field of the enum itself as mapping value.

For all others enums in your project that need similar mapping logic, you only have to repeat steps 3 to 5, that is:

  • implement the interface IDbValue on your enum;
  • extend the EnumDbValueConverter with only 3 lines of code (you may also do this within your entity to avoid creating a separated class);
  • annotate the enum attribute with @Convert from javax.persistence package.

Hope this helps.


Possibly close related code of Pascal

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

    public enum Right {
        READ(100), WRITE(200), EDITOR(300);

        private Integer value;

        private Right(Integer value) {
            this.value = value;
        }

        // Reverse lookup Right for getting a Key from it's values
        private static final Map<Integer, Right> lookup = new HashMap<Integer, Right>();
        static {
            for (Right item : Right.values())
                lookup.put(item.getValue(), item);
        }

        public Integer getValue() {
            return value;
        }

        public static Right getKey(Integer value) {
            return lookup.get(value);
        }

    };

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "AUTHORITY_ID")
    private Long id;

    @Column(name = "RIGHT_ID")
    private Integer rightId;

    public Right getRight() {
        return Right.getKey(this.rightId);
    }

    public void setRight(Right right) {
        this.rightId = right.getValue();
    }

}