6

We're trying to figure out a robust way of persisting enums using JPA. The common approach of using @Enumerated is not desirable, because it's too easy to break the mappings when refactoring. Each enum should have a separate database value that can be different than the enum name/order, so that you can safely change the name or internal ordering (e.g. the ordinal values) of the enum without breaking anything. E.g. this blog post has an example on how to achieve this, but we feel the suggested solution adds too much clutter to the code. We'd like to achieve a similar result by using the new AttributeConverter mechanism introduced in JPA 2.1. We have an interface that each enum should implement that defines a method for getting the value that is used to store the enum in the database. Example:

public interface PersistableEnum {
    String getDatabaseValue();
}

...

public enum SomeEnum implements PersistableEnum {
    FOO("foo"), BAR("bar");

    private String databaseValue;

    private SomeEnum(String databaseValue) {
        this.databaseValue = databaseValue;
    }

    public void getDatabaseValue() {
        return databaseValue;
    }
}

We also have a base converter that has the logic for converting enums to Strings and vice versa, and separate concrete converter classes for each enum type (AFAIK, a fully generic enum converter is not possible to implement, this is also noted in this SO answer). The concrete converters then simply call the base class that does the conversion, like this:

public abstract class EnumConverter<E extends PersistableEnum> {

    protected String toDatabaseValue(E value) {
        // Do the conversion...
    }

    protected E toEntityAttribute(Class<E> enumClass, String value) {
        // Do the conversion...
    }
}

...

@Converter(autoApply = true)
public class SomeEnumConverter extends EnumConverter<SomeEnum> 
            implements AttributeConverter<SomeEnum, String> {

    public String convertToDatabaseColumn(SomeEnum attribute) {
        return toDatabaseValue(attribute);
    }

    public SomeEnum convertToEntityAttribute(String dbData) {
        return toEntityAttribute(SomeEnum.class, dbData);
    }
}

However, while this approach works very nicely in a technical sense, there's still a pretty nasty pitfall: Whenever someone creates a new enum class whose values need to be stored to the database, that person also needs to remember to make the new enum implement the PersistableEnum interface and write a converter class for it. Without this, the enum will get persisted without a problem, but the conversion will default to using @Enumerated(EnumType.ORDINAL), which is exactly what we want to avoid. How could we prevent this? Is there a way to make JPA (in our case, Hibernate) NOT default to any mapping, but e.g. throw an exception if no @Enumerated is defined on a field and no converter can be found for the type? Or could we create a "catch all" converter that is called for all enums that don't have their own specific converter class and always throw an exception from there? Or do we just have to suck it up and try to remember the additional steps each time?

Community
  • 1
  • 1
Markus Yrjölä
  • 859
  • 1
  • 12
  • 25
  • There is an approach here in the below link. It doesn't however, address the pitfalls you mentioned but at least it provides a good generic way to handle Enums: http://javacodebox.blogspot.com.au/2013/12/hibernate-and-mapping-enum-to.html – xbmono Jan 22 '16 at 04:42

1 Answers1

0

You want to ensure that all Enums are instances of PersistableEnum.

You need to set a Default Entity Listener (an entity listener whose callbacks apply to all entities in the persistence unit).

In the Default Entity Listener class implement the @PrePersist method and make sure all the Enums are instances of PersistableEnum.

fidudidu
  • 399
  • 3
  • 10