Map enum in JPA with fixed values?
I'm looking for the different ways to map an enum using JPA. I especially want to set the integer value of each enum entry and to save only the integer value.
@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; }
};
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
// the enum to map :
private Right right;
}
A simple solution is to use the Enumerated annotation with EnumType.ORDINAL:
@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;
But in this case JPA maps the enum index (0,1,2) and not the value I want (100,200,300).
Th two solutions I found do not seem simple...
First Solution
A solution, proposed here, uses @PrePersist and @PostLoad to convert the enum to an other field and mark the enum field as transient:
@Basic
private int intValueForAnEnum;
@PrePersist
void populateDBFields() {
intValueForAnEnum = right.getValue();
}
@PostLoad
void populateTransientFields() {
right = Right.valueOf(intValueForAnEnum);
}
Second Solution
The second solution proposed here proposed a generic conversion object, but still seems heavy and hibernate-oriented (@Type doesn't seem to exist in Java EE):
@Type(
type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
parameters = {
@Parameter(
name = "enumClass",
value = "Authority$Right"),
@Parameter(
name = "identifierMethod",
value = "toInt"),
@Parameter(
name = "valueOfMethod",
value = "fromInt")
}
)
Is there any other solutions ?
I've several ideas in mind but I don't know if they exist in JPA:
- use the setter and getter methods of right member of Authority Class when loading and saving the Authority object
- an equivalent idea would be to tell JPA what are the methods of Right enum to convert enum to int and int to enum
- Because I'm using Spring, is there any way to tell JPA to use a specific converter (RightEditor) ?
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
, EclipseLinkConverter
, 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.