JPA Attribute Converter – The better way to persist enums
Take your skills to the next level!
The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.
JPA provides 2 standard mappings for enums, but both have serious downsides. Using the @Enumerated annotation, you can use EnumType.ORDINAL or EnumType.STRING to map the enum value to its database representation. The ordinal of an Enum depends on the ordering of its values and can create problems if we need to add new ones. The String representation of an Enum is often quite verbose, and renaming a value will break the database mapping. These drawbacks can be avoided by using an Attribute Converter.
Implementing the Converter
An Attribute Converter allows us to implement methods to convert the value of an entity attribute to its database representation and back. I will not get into a lot of detail on how to implement an Attribute Converter because I already did this in one of my former articles.
By implementing our own mapping, we can choose a compact database representation and make sure that changing the enum in any way will not break the existing mapping. Therefore, we add the shortName, which will be used as the database representation, as an additional property to the Enum. We also need a method to get the shortName property and to get the enum for a given shortName.
public enum Vehicle { BUS("B"), CAR("C"), TRAIN("T"), PLANE("P"); private String shortName; private Vehicle(String shortName) { this.shortName = shortName; } public String getShortName() { return shortName; } public static Vehicle fromShortName(String shortName) { switch (shortName) { case "B": return Vehicle.BUS; case "C": return Vehicle.CAR; case "T": return Vehicle.TRAIN; case "P": return Vehicle.PLANE; default: throw new IllegalArgumentException("ShortName [" + shortName + "] not supported."); } } }
Now we can implement the converter to use the shortName property to store the Enum in the database:
@Converter(autoApply = true) public class VehicleConverter implements AttributeConverter<Vehicle, String> { @Override public String convertToDatabaseColumn(Vehicle vehicle) { return vehicle.getShortName(); } @Override public Vehicle convertToEntityAttribute(String dbData) { return Vehicle.fromShortName(dbData); } }
The VehicleConverter maps the enum value to the shortName to store it in the database. By declaring it with @Converter(autoApply = true), we tell the JPA provider to use it to map all Vehicle enums. So, we do not need to specify the converter for each entity attribute of type Vehicle.
But there is one thing we need to take care of, and if you have read my previous article about JPA Attribute Converter you might have wondered already. Attribute Converter cannot be applied to attributes annotated with @Enumerated. So we have to make sure that there is no @Enumerated annotation at our entity attributes of type Vehicle.
@Entity public class Trip { private Vehicle vehicle; ... }
Using the Converter
OK, implementing the Converter was easy. But how do we use it in the application?
This is one of the best parts of the Attribute Converter. We do not have to do anything. The persistence provider will use it for all read and write operations, e.g. in JPQL queries.
TypedQuery<Trip> q = em.createQuery("SELECT t FROM Trip t WHERE t.vehicle = :v", Trip.class); q.setParameter("v", Vehicle.PLANE); List<Trip> trips = q.getResultList();
Conclusion
We implemented a simple Attribute Converter that uses our own rules to convert the Vehicle enum to its database representation. So we can make sure that changing the values of the Vehicle enum will not break the existing/remaining mappings.
What do you think about using JPA Attribute Converter to persist enums? Please leave me a comment!
And if you like to read more about the features introduced with JPA 2.1, have a look at my JPA 2.1 overview: JPA 2.1 – 12 features every developer should know.
++
Thanks for this post.
I had been thinking about this too and implemented this a couple of days back in my toy example project. This blog post confirms that I am not going down the wrong path.
I also tried creating a generic converter for all my enums like Gamal Mateo suggested, but could not figure out how to get it to work. So ended up having a converter for each enum in my app. 🙁
I am curious about the comment that Frisian made about the visitor. Am not familiar with it, so plan to research it.
Thanks for your post. informative.
sgb.
I'd be happy if I could just change the JPA default from ORDINAL to STRING.
Hi Frisian
You are right, using a visitor would be even better. I will use that for my real applications. Thanks!
Regards,
Thorben
Hi Gamal,
thanks for your comment.
I would love to create a generic type converter. But up to now, I have no idea how to implement the convertToEntityAttribute(String dbData) method in a generic way. The method has to create a specific enum and I have no idea how to get the required one. Any suggestions?
Regards,
Thorben
PS: If you want to try something, you can find the source on github: https://github.com/somethoughtsonjava/JPA2.1-EnumConverter
Even nicer would be to add a visitor to the enum and to use it inside the converter. That way the compiler would notify you in case the enum has changed.
Maybe the best solution would be use a generic type converter in order to manage all enums. In your example for each enum we have to create a converter.