Introduction
Until now, an enum value could be stored in the database by its ordinal number or his name in JPA. When you have a ‘legacy’ database or when you are doing refactorings you could be in trouble. The only option then was to rely on the implementation to allow a custom value as the representation of the numeration in the database.In the latest release of JPA, 2.1, there is support for custom converters so you can solve these kind of issues.
JPA 2 Converter
What do you need to do to have a custom converter? The enumeration class doesn’t need any change. In the entity were you use the enum, you need to specify the converter you like to use, like this:@Entitypublic class Person {...@Column@Convert(converter = GenderEnumConverter.class)
private Gender gender;
...}
The converter class can look as this
@Converterpublic class GenderEnumConverter implements AttributeConverter< Gender, String> {@Overridepublic String convertToDatabaseColumn(Gender attribute) {
String result = "";switch (attribute) {
case MALE:
result = "M";
break;
case FEMALE:
result = "F";
break;
default:
}return result;
}@Overridepublic Gender convertToEntityAttribute(String dbData) {
Gender result = null;
switch (dbData) {
case "M":result = Gender.MALE;break;
case "F":result = Gender.FEMALE;break;
default:
}return result;
}}
And the last thing you need to do is to ‘register’ the converter with JPA as no scanning is performed of the available classes. You can specify the class name, just as you specify the entity classes in the persistence.xml file.
<persistence-unit name="converter-unit" ><description>Forge Persistence Unit</description><class>be.rubus.web.ee7.jpa.converter.model.Person</class><class>be.rubus.web.ee7.jpa.converter.jpa.GenderEnumConverter</class></persistence-unit>
Not OO
The above code is not ideal in several ways. If you have an enum class with a lot of values, you have to write a large if-then-else structure which is not very OO like. And you also need to write a lot of similar code which wants me to search for a more generic solution.
If we could define the database value together with the enum value, it would be a great improvement. It makes the code also much more logic as you can define the database value together with the enum value.
Since we need such functionality for each numeration that we use in the persistent objects, we can create an interface like DatabaseEnum.
public interface DatabaseEnum {Serializable getDatabaseValue();}
And change the enum to implement this interface.
public enum Gender implements DatabaseEnum {MALE("M"), FEMALE("F");private Serializable databaseValue;
Gender(Serializable databaseValue) {this.databaseValue = databaseValue;
}public Serializable getDatabaseValue() {
return databaseValue;
}}
This should allow us to create a generic converter for the enums, except that the Java compiler removes the information regarding the generic types, know as type erasure. Otherwise a converter like this could be possible.
@Converterpublic class EnumConverter<T extends DatabaseEnum> implements AttributeConverter< T, Serializable> {@Overridepublic Serializable convertToDatabaseColumn(T attribute) {
return attribute.getDatabaseValue();
}@Overridepublic T convertToEntityAttribute(Serializable dbData) {
// There is no way we can create such a method
return determineEnum(T, dbData);
}}
If we would be able to define that a custom constructor needs to be called, where we supply as parameter the class T, we would get away with it. But this is not the case.
public EnumConverter(Class<T> enumType) {..}
Almost generic converter
Based on the optimal code above, we are able to create a converter that uses a generic utility method. It means that we still need to create a converter for each enum class we like to use, but the code is much cleaner then the first example we show in the text.
@Converterpublic class GenderEnumConverter implements AttributeConverter<DatabaseEnum, Serializable> {@Overridepublic Serializable convertToDatabaseColumn(DatabaseEnum attribute) {
return attribute.getDatabaseValue();
}@Overridepublic Gender convertToEntityAttribute(Serializable dbData) {
return DatabaseEnumUtil.getEnumValue(Gender.class, dbData);}}
And the utility method can be small and generic thanks to the not very known method getEnumConstants() of the JVM core classes.
public static <T extends DatabaseEnum> T getEnumValue(Class<T> enumClass, Serializable dbValue) {T result = null;
if (dbValue != null) {for (DatabaseEnum enumInstance : enumClass.getEnumConstants()) {
if (dbValue.equals(enumInstance.getDatabaseValue())) {
result = (T) enumInstance;break;
}}}return result;
}
Conclusion
In the latest version of JPA, a custom converter can be defined which can be very handy in a lot of situations. You can use it to create a converter for Joda-time classes or for specifying the database value of an enum value.
The only thing that keeps us from creating a generic converter for enums is the type erasure thing of Java. Otherwise we could create a beautiful converter for all our enums.
No comments:
Post a Comment