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, 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:
The limitation of using name() and ordinal(). Why not just mark a getter with #Id, the way we do with #Entity?
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();
}
}
I would do the folowing:
Declare separetly the enum, in it´s own file:
public enum RightEnum {
READ(100), WRITE(200), EDITOR (300);
private int value;
private RightEnum (int value) { this.value = value; }
#Override
public static Etapa valueOf(Integer value){
for( RightEnum r : RightEnum .values() ){
if ( r.getValue().equals(value))
return r;
}
return null;//or throw exception
}
public int getValue() { return value; }
}
Declare a new JPA entity named Right
#Entity
public class Right{
#Id
private Integer id;
//FIElDS
// constructor
public Right(RightEnum rightEnum){
this.id = rightEnum.getValue();
}
public Right getInstance(RightEnum rightEnum){
return new Right(rightEnum);
}
}
You will also need a converter for receiving this values (JPA 2.1 only and there´s a problem I´ll not discuss here with these enum´s to be directly persisted using the converter, so it will be a one way road only)
import mypackage.RightEnum;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/**
*
*
*/
#Converter(autoApply = true)
public class RightEnumConverter implements AttributeConverter<RightEnum, Integer>{
#Override //this method shoudn´t be used, but I implemented anyway, just in case
public Integer convertToDatabaseColumn(RightEnum attribute) {
return attribute.getValue();
}
#Override
public RightEnum convertToEntityAttribute(Integer dbData) {
return RightEnum.valueOf(dbData);
}
}
The Authority entity:
#Entity
#Table(name = "AUTHORITY_")
public class Authority implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "AUTHORITY_ID")
private Long id;
// the **Entity** to map :
private Right right;
// the **Enum** to map (not to be persisted or updated) :
#Column(name="COLUMN1", insertable = false, updatable = false)
#Convert(converter = RightEnumConverter.class)
private RightEnum rightEnum;
}
By doing this way, you can´t set directly to the enum field. However, you can set the Right field in Authority using
autorithy.setRight( Right.getInstance( RightEnum.READ ) );//for example
And if you need to compare, you can use:
authority.getRight().equals( RightEnum.READ ); //for example
Which is pretty cool, I think. It´s not totally correct, since the converter it´s not intended to be use with enum´s. Actually, the documentation says to never use it for this purpose, you should use the #Enumerated annotation instead. The problem is that there are only two enum types: ORDINAL or STRING, but the ORDINAL is tricky and not safe.
However, if it doesn´t satisfy you, you can do something a little more hacky and simpler (or not).
Let´s see.
The RightEnum:
public enum RightEnum {
READ(100), WRITE(200), EDITOR (300);
private int value;
private RightEnum (int value) {
try {
this.value= value;
final Field field = this.getClass().getSuperclass().getDeclaredField("ordinal");
field.setAccessible(true);
field.set(this, value);
} catch (Exception e) {//or use more multicatch if you use JDK 1.7+
throw new RuntimeException(e);
}
}
#Override
public static Etapa valueOf(Integer value){
for( RightEnum r : RightEnum .values() ){
if ( r.getValue().equals(value))
return r;
}
return null;//or throw exception
}
public int getValue() { return value; }
}
and the Authority entity
#Entity
#Table(name = "AUTHORITY_")
public class Authority implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "AUTHORITY_ID")
private Long id;
// the **Enum** to map (to be persisted or updated) :
#Column(name="COLUMN1")
#Enumerated(EnumType.ORDINAL)
private RightEnum rightEnum;
}
In this second idea, its not a perfect situation since we hack the ordinal attribute, but it´s a much smaller coding.
I think that the JPA specification should include the EnumType.ID where the enum value field should be annotated with some kind of #EnumId annotation.
Related
I have a very simple Entity (Person.java) that I am wanting to persist via JPA/Hibernate.
The Entity contains two fields: ID and Identification String.
The ID is a simple Integer, and is no problem. The Identification String is currently a String, but for various reasons, I want to instead use a wrapper class for String (IDString), where there are various validation methods among other things.
I am wondering how I can get JPA/Hibernate to use the wrapped string (inside the custom class IDString) when persisting the Person table in the database. I know this can probably be solved by letting the IDString be #Embeddable and then embed IDString in the Person entity with #Embedded, but I am looking for another method, mostly because IDString is in an entirely different package, and I am reluctant to have to go there and change stuff.
Googling, I found https://www.baeldung.com/hibernate-custom-types, but it seems to be mostly about more complicated cases, where you want to convert one class into another type, and I do feel that there is probably a smarter way that I am simply overlooking.
Here is the entity (in theory)
#Entity(name="Person")
#Table(name="DB_TABLE_PERSON")
public class Person implements Serializable {
#Id
Integer id;
// WHAT SHOULD I PUT HERE? I WANT TO SIMPLY USE THE STRING INSIDE IDSTRING AS THE FIELD TO PERSIST
IDString idString;
// getter and setter for ID.
public void getIdString() {
return idString.getValue();
}
public void setIdString(String in) {
idString.setValue(in);
}
}
And here is the class IDString (in theory):
public class IDString {
// I really want to be a POJO
private final String the_string;
public IdString(String input) {
if (isValid(input)) {
the_string = input;
} else {
throw new SomeCoolException("Invalid format of the ID String");
}
public boolean isValid(String input) {
// bunch of code to validate the input string
}
public String getValue() {
return the_string;
}
public void setValue(String input) {
if (isValid(input)) the_string = s;
else throw new SomeCoolException("Invalid format of the ID String");
}
I know that I could place the validation if the IDString inside the Entity, but the IDString will be used elsewhere (it's a general custom class), so I don't want to do that. Is there a simple way?
#Converter(autoApply=true) // autoApply is reasonable, if not use #Converter on field
public class IDStringConverter implements AttributeConverter<IDString,String> {
#Override
public String convertToDatabaseColumn(IDString attribute) {
return attribute != null ? attribute.getValue() : null;
}
#Override
public IDString convertToEntityAttribute(String dbData) {
return dbData != null ? new IDString(dbData) : null;
}
}
With this you should not need any other modifications in your code. One limitation of the AttributeConverter is that it maps from exactly 1 Java field to exactly 1 DB column. If you wanted to map to more columns (not the case here), you would need embeddables.
You could also put a #Column annotation on the getter:
#Entity
public class Person {
private final IdString idString = new IdString();
#Column(name = "ID_STRiNG")
public IdString getIdString() {
return idString.getValue();
}
public void setIdString(String input) {
idString.setValue(input);
}
Another solution could be to convert to/from IdString using #PostLoad and #PrePersit event handlers:
#Entity
public class Person {
#Column(name = "ID_STRiNG")
private String the_string; // no getters & setters
#Transient
private final IdString idString = new IdString();
#PostLoad
public void postLoad() {
idString.setValue(the_string);
}
#PrePersist
public void prePersist() {
the_string = idString.getValue();
}
// getters & setters for idString
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, 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:
The limitation of using name() and ordinal(). Why not just mark a getter with #Id, the way we do with #Entity?
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();
}
}
I would do the folowing:
Declare separetly the enum, in it´s own file:
public enum RightEnum {
READ(100), WRITE(200), EDITOR (300);
private int value;
private RightEnum (int value) { this.value = value; }
#Override
public static Etapa valueOf(Integer value){
for( RightEnum r : RightEnum .values() ){
if ( r.getValue().equals(value))
return r;
}
return null;//or throw exception
}
public int getValue() { return value; }
}
Declare a new JPA entity named Right
#Entity
public class Right{
#Id
private Integer id;
//FIElDS
// constructor
public Right(RightEnum rightEnum){
this.id = rightEnum.getValue();
}
public Right getInstance(RightEnum rightEnum){
return new Right(rightEnum);
}
}
You will also need a converter for receiving this values (JPA 2.1 only and there´s a problem I´ll not discuss here with these enum´s to be directly persisted using the converter, so it will be a one way road only)
import mypackage.RightEnum;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/**
*
*
*/
#Converter(autoApply = true)
public class RightEnumConverter implements AttributeConverter<RightEnum, Integer>{
#Override //this method shoudn´t be used, but I implemented anyway, just in case
public Integer convertToDatabaseColumn(RightEnum attribute) {
return attribute.getValue();
}
#Override
public RightEnum convertToEntityAttribute(Integer dbData) {
return RightEnum.valueOf(dbData);
}
}
The Authority entity:
#Entity
#Table(name = "AUTHORITY_")
public class Authority implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "AUTHORITY_ID")
private Long id;
// the **Entity** to map :
private Right right;
// the **Enum** to map (not to be persisted or updated) :
#Column(name="COLUMN1", insertable = false, updatable = false)
#Convert(converter = RightEnumConverter.class)
private RightEnum rightEnum;
}
By doing this way, you can´t set directly to the enum field. However, you can set the Right field in Authority using
autorithy.setRight( Right.getInstance( RightEnum.READ ) );//for example
And if you need to compare, you can use:
authority.getRight().equals( RightEnum.READ ); //for example
Which is pretty cool, I think. It´s not totally correct, since the converter it´s not intended to be use with enum´s. Actually, the documentation says to never use it for this purpose, you should use the #Enumerated annotation instead. The problem is that there are only two enum types: ORDINAL or STRING, but the ORDINAL is tricky and not safe.
However, if it doesn´t satisfy you, you can do something a little more hacky and simpler (or not).
Let´s see.
The RightEnum:
public enum RightEnum {
READ(100), WRITE(200), EDITOR (300);
private int value;
private RightEnum (int value) {
try {
this.value= value;
final Field field = this.getClass().getSuperclass().getDeclaredField("ordinal");
field.setAccessible(true);
field.set(this, value);
} catch (Exception e) {//or use more multicatch if you use JDK 1.7+
throw new RuntimeException(e);
}
}
#Override
public static Etapa valueOf(Integer value){
for( RightEnum r : RightEnum .values() ){
if ( r.getValue().equals(value))
return r;
}
return null;//or throw exception
}
public int getValue() { return value; }
}
and the Authority entity
#Entity
#Table(name = "AUTHORITY_")
public class Authority implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "AUTHORITY_ID")
private Long id;
// the **Enum** to map (to be persisted or updated) :
#Column(name="COLUMN1")
#Enumerated(EnumType.ORDINAL)
private RightEnum rightEnum;
}
In this second idea, its not a perfect situation since we hack the ordinal attribute, but it´s a much smaller coding.
I think that the JPA specification should include the EnumType.ID where the enum value field should be annotated with some kind of #EnumId annotation.
I'm trying to map a string stored in the database (ex. ABC1, BCD2) to an enum (ABC_1, BCD_2).
With hibernate I was able to do this with the following hibernate mapping
<typedef name="LinkEnum" class="GenericEnumUserType">
<param name="enumClass">types.LinkEnum</param>
<param name="identifierMethod">value</param>
<param name="valueOfMethod">fromValue</param>
</typedef>
and in the LinkEnum
#XmlType(name = "LinkEnum")
#XmlEnum
public enum LinkEnum {
#XmlEnumValue("ABC1")
ABC_1("ABC1"),
#XmlEnumValue("BCD2")
BCD_2("BCD2");
private final String value;
LinkEnum(String v) {
value = v;
}
public String value() {
return value;
}
public static LinkEnum fromValue(String v) {
for (LinkeEnum c: LinkEnum.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}
In the JPA class, I'm trying to do the same kind of mapping, however it's having a problem mapping the enum still. Is there an equivalent way to do this with JPA?
private LinkEnum link;
#Enumerated(EnumType.STRING)
#Column(name = "LINK", nullable = false, length = 8)
public LinkEnum getLink() {
return this.link;
}
You could also use a javax.persistence.AttributeConverter (gives your more freedom than the above solution).
For this, implement a class that implements AttributeConverter and annotate your member in the class as follows:
#Convert(converter = NameOfYourConverter.class)
There's a good explanation on the documentation of #Enumerated
public enum EmployeeStatus {FULL_TIME, PART_TIME, CONTRACT}
public enum SalaryRate {JUNIOR, SENIOR, MANAGER, EXECUTIVE}
#Entity public class Employee {
//#Enumerated is not mandatory. If it's not specified, It assumes to be an ORDINAL (by default)
public EmployeeStatus getStatus() {...}
...
#Enumerated(EnumType.STRING)
public SalaryRate getPayScale() {...}
...
}
https://docs.oracle.com/javaee/7/api/javax/persistence/Enumerated.html
Define your Enum like this :
public enum LinkEnum {ABC_1("ABC1"), BCD_2("BCD2")}
And your entity, you can annotated like this :
#Enumerated(EnumType.STRING)
public LinkEnum getLinkEnum() {...}
Working with JPA, I would like to be able to save a BitSet to the DB and pull it back of course.
Suppose I have:
#Entity
#Table(name = "myTable")
public class MyClass {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Object_Id")
protected long id;
#Column(name = "Tags")
protected BitSet tags;
... getters & setters etc...
}
Should I define a "columnDefinition" as well? I don't really understand how it is persisted (using toString()?) and moreover how does it get loaded back from the DB.
Can you please help me with this one?
Thanks!
More efficient way (that uses int instead of byte[]) requires a pretty simple custom class:
#Entity
#Access(AccessType.FIELD)
public class SampleEntity {
#Transient
private IntBitSet isolationLevel = new IntBitSet(0);
public static final int USER_BIT = 0;
public static final int DEVICE_BIT = 1;
// 2, 3, 4, ...
public boolean isUserIsolated() {
return isolationLevel.bitGet(USER_BIT);
}
public boolean isDeviceIsolated() {
return isolationLevel.bitGet(DEVICE_BIT);
}
public void setUserIsolated(boolean b) {
isolationLevel.bitSet(USER_BIT, b);
}
public void setDeviceIsolated(boolean b) {
isolationLevel.bitSet(DEVICE_BIT, b);
}
#Access(AccessType.PROPERTY)
#Column
public int getIsolationLevel() {
return isolationLevel.getValue();
}
public void setIsolationLevel(int isolationLevel) {
this.isolationLevel = new IntBitSet(isolationLevel);
}
private static class IntBitSet {
private int value;
public IntBitSet(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public boolean bitGet(int i) {
return ((value >> i) & 1) == 1;
}
public void bitSet(int i, boolean b) {
if (b) {
bitSet(i);
} else {
bitUnset(i);
}
}
private void bitSet(int i) {
value = value | (1 << i);
}
private void bitUnset(int i) {
value = value & ~(1 << i);
}
}
}
By default JPA uses Java serialization to persist properties of unknown Serializable types (so that you have a serialized representation stored as a byte[]).
Usually it's not what you want, because there can be more efficient ways to represent your data. For example, BitSet can be efficiently represented as a number (if its size is limited), or byte[], or something else (unfortunately, BitSet doesn't provide methods to do these conversions, therefore you need to implement them manually).
When you've decided what kind of data representation you want to have in the database you need to tell JPA to apply the necessary conversion. There are two options:
Implement conversion in getters and setters. For example, as follows:
#Entity
#Table(name = "myTable")
#Access(AccessType.FIELD)
public class MyClass {
...
#Transient // Do not store this field
protected BitSet tags;
#Access(AccessType.PROPERTY) // Store the property instead
#Column(name = "Tags")
byte[] getTagsInDbRepresentation() {
... // Do conversion
}
void setTagsInDbRepresentation(byte[] data) {
... // Do conversion
}
...
}
Use provider-specific extension to perform the conversion implicitly (for example, custom types in Hibernate). This approach allows you to reuse your type conversion logic in different entities.
You can use any of the following conversion in the getter method
byte[] bytes = bitSet.toByteArray();
long[] longs = bitSet.toLongArray();
I have an existing database of a film rental system. Each film has a has a rating attribute. In SQL they used a constraint to limit the allowed values of this attribute.
CONSTRAINT film_rating_check CHECK
((((((((rating)::text = ''::text) OR
((rating)::text = 'G'::text)) OR
((rating)::text = 'PG'::text)) OR
((rating)::text = 'PG-13'::text)) OR
((rating)::text = 'R'::text)) OR
((rating)::text = 'NC-17'::text)))
I think it would be nice to use a Java enum to map the constraint into the object world. But it's not possible to simply take the allowed values because of the special char in "PG-13" and "NC-17". So I implemented the following enum:
public enum Rating {
UNRATED ( "" ),
G ( "G" ),
PG ( "PG" ),
PG13 ( "PG-13" ),
R ( "R" ),
NC17 ( "NC-17" );
private String rating;
private Rating(String rating) {
this.rating = rating;
}
#Override
public String toString() {
return rating;
}
}
#Entity
public class Film {
..
#Enumerated(EnumType.STRING)
private Rating rating;
..
With the toString() method the direction enum -> String works fine, but String -> enum does not work. I get the following exception:
[TopLink Warning]: 2008.12.09
01:30:57.434--ServerSession(4729123)--Exception [TOPLINK-116] (Oracle
TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))):
oracle.toplink.essentials.exceptions.DescriptorException Exception
Description: No conversion value provided for the value [NC-17] in
field [FILM.RATING]. Mapping:
oracle.toplink.essentials.mappings.DirectToFieldMapping[rating-->FILM.RATING]
Descriptor: RelationalDescriptor(de.fhw.nsdb.entities.Film -->
[DatabaseTable(FILM)])
cheers
timo
have you tried to store the ordinal value. Store the string value works fine if you don't have an associated String to the value:
#Enumerated(EnumType.ORDINAL)
You have a problem here and that is the limited capabilities of JPA when it comes to handling enums. With enums you have two choices:
Store them as a number equalling Enum.ordinal(), which is a terrible idea (imho); or
Store them as a string equalling Enum.name(). Note: not toString() as you might expect, especially since the default behaviourfor Enum.toString() is to return name().
Personally I think the best option is (2).
Now you have a problem in that you're defining values that don't represent vailid instance names in Java (namely using a hyphen). So your choices are:
Change your data;
Persist String fields and implicitly convert them to or from enums in your objects; or
Use nonstandard extensions like TypeConverters.
I would do them in that order (first to last) as an order of preference.
Someone suggested Oracle TopLink's converter but you're probably using Toplink Essentials, being the reference JPA 1.0 implementation, which is a subset of the commercial Oracle Toplink product.
As another suggestion, I'd strongly recommend switching to EclipseLink. It is a far more complete implementation than Toplink Essentials and Eclipselink will be the reference implementation of JPA 2.0 when released (expected by JavaOne mid next year).
Sounds like you need to add support for a custom type:
Extending OracleAS TopLink to Support Custom Type Conversions
public enum Rating {
UNRATED ( "" ),
G ( "G" ),
PG ( "PG" ),
PG13 ( "PG-13" ),
R ( "R" ),
NC17 ( "NC-17" );
private String rating;
private static Map<String, Rating> ratings = new HashMap<String, Rating>();
static {
for (Rating r : EnumSet.allOf(Rating.class)) {
ratings.put(r.toString(), r);
}
}
private static Rating getRating(String rating) {
return ratings.get(rating);
}
private Rating(String rating) {
this.rating = rating;
}
#Override
public String toString() {
return rating;
}
}
I don't know how to do the mappings in the annotated TopLink side of things however.
i don't know internals of toplink, but my educated guess is the following: it uses the Rating.valueOf(String s) method to map in the other direction. it is not possible to override valueOf(), so you must stick to the naming convention of java, to allow a correct valueOf method.
public enum Rating {
UNRATED,
G,
PG,
PG_13 ,
R ,
NC_17 ;
public String getRating() {
return name().replace("_","-");;
}
}
getRating produces the "human-readable" rating. note that the "-" chanracter is not allowed in the enum identifier.
of course you will have to store the values in the DB as NC_17.
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:
The limitation of using name() and ordinal(). Why not just mark a getter with #Id, the way we do with #Entity?
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
Using your existing enum Rating. You can use AttributeCoverters.
#Converter(autoApply = true)
public class RatingConverter implements AttributeConverter<Rating, String> {
#Override
public String convertToDatabaseColumn(Rating rating) {
if (rating == null) {
return null;
}
return rating.toString();
}
#Override
public Rating convertToEntityAttribute(String code) {
if (code == null) {
return null;
}
return Stream.of(Rating.values())
.filter(c -> c.toString().equals(code))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
In JPA 2.0, a way to persist an enum using neither the name() nor ordinal() can be done by wrapping the enum in a Embeddable class.
Assume we have the following enum, with a code value intended to be stored in the database :
public enum ECourseType {
PACS004("pacs.004"), PACS008("pacs.008");
private String code;
ECourseType(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
Please note that the code values could not be used as names for the enum since they contain dots. This remark justifies the workaround we are providing.
We can build an immutable class (as a value object) wrapping the code value of the enum with a static method from() to build it from the enum, like this :
#Embeddable
public class CourseType {
private static Map<String, ECourseType> codeToEnumCache =
Arrays.stream(ECourseType.values())
.collect(Collectors.toMap( e -> e.getCode(), e -> e));
private String value;
private CourseType() {};
public static CourseType from(ECourseType en) {
CourseType toReturn = new CourseType();
toReturn.value = en.getCode();
return toReturn;
}
public ECourseType getEnum() {
return codeToEnumCache.get(value);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass() ) return false;
CourseType that = (CourseType) o;
return Objects.equals(value, that.value);
}
#Override
public int hashCode() {
return Objects.hash(value);
}
}
Writing proper equals() and hashcode() is important to insure the "value object" aim of this class.
If needed, an equivalence method between the CourseType et ECourseType may be added (but not mixed with equals()) :
public boolean isEquiv(ECourseType eCourseType) {
return Objects.equals(eCourseType, getEnum());
}
This class can now be embedded in an entity class :
public class Course {
#Id
#GeneratedValue
#Column(name = "COU_ID")
private Long pk;
#Basic
#Column(name = "COURSE_NAME")
private String name;
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "value", column = #Column(name = "COURSE_TYPE")),
})
private CourseType type;
public void setType(CourseType type) {
this.type = type;
}
public void setType(ECourseType type) {
this.type = CourseType.from(type);
}
}
Please note that the setter setType(ECourseType type) has been added for convenience. A similar getter could be added to get the type as ECourseType.
Using this modeling, hibernate generates (for H2 db) the following SQL table :
CREATE TABLE "PUBLIC"."COU_COURSE"
(
COU_ID bigint PRIMARY KEY NOT NULL,
COURSE_NAME varchar(255),
COURSE_TYPE varchar(255)
)
;
The "code" values of the enum will be stored in the COURSE_TYPE.
And the Course entities can be searched with a query as simple as this :
public List<Course> findByType(CourseType type) {
manager.clear();
Query query = manager.createQuery("from Course c where c.type = :type");
query.setParameter("type", type);
return (List<Course>) query.getResultList();
}
Conclusion:
This shows how to persist an enum using neither the name nor the ordinal but insure a clean modelling of an entity relying on it.
This is can be particularly useful for legacy when the values stored in db are not compliant to the java syntax of enum names and ordinals.
It also allows refactoring the enum names without having to change values in db.
What about this
public String getRating{
return rating.toString();
}
pubic void setRating(String rating){
//parse rating string to rating enum
//JPA will use this getter to set the values when getting data from DB
}
#Transient
public Rating getRatingValue(){
return rating;
}
#Transient
public Rating setRatingValue(Rating rating){
this.rating = rating;
}
with this you use the ratings as String both on your DB and entity, but use the enum for everything else.
use this annotation
#Column(columnDefinition="ENUM('User', 'Admin')")
Enum
public enum ParentalControlLevelsEnum {
U("U"), PG("PG"), _12("12"), _15("15"), _18("18");
private final String value;
ParentalControlLevelsEnum(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static ParentalControlLevelsEnum fromString(final String value) {
for (ParentalControlLevelsEnum level : ParentalControlLevelsEnum.values()) {
if (level.getValue().equalsIgnoreCase(value)) {
return level;
}
}
return null;
}
}
compare -> Enum
public class RatingComparator implements Comparator {
public int compare(final ParentalControlLevelsEnum o1, final ParentalControlLevelsEnum o2) {
if (o1.ordinal() < o2.ordinal()) {
return -1;
} else {
return 1;
}
}
}
Resolved!!!
Where I found the answer: http://programming.itags.org/development-tools/65254/
Briefly, the convertion looks for the name of enum, not the value of attribute 'rating'.
In your case: If you have in the db values "NC-17", you need to have in your enum:
enum Rating {
(...)
NC-17 ( "NC-17" );
(...)