Related
I would like to unmarshall an object using JAXB based on the enum value/string present in the xml. I have several classes inheriting from one abstract class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({ InheritingClassOne.class, InheritingClassTwo.class })
public abstract class BasicClass {
protected String type; //determines class type
protected String description;
//getters & setters
}
two examplary subclasses:
#XmlRootElement(name = "someRoot")
#XmlAccessorType(XmlAccessType.FIELD)
public class InheritingClassOne extends BasicClass {
private String message;
private Float mark;
//getters & setters
}
#XmlRootElement(name = "someRoot")
#XmlAccessorType(XmlAccessType.FIELD)
public class InheritingClassTwo extends BasicClass {
private Integer value;
//getters & setters
}
I know that JAXB can unmarshall objects based on xsi:type attribute, which I cannot use, since the input xml is stripped from all atributes. I have tried using Moxy #XmlDiscriminatorNode & #XmlDiscriminatorValue, but these seem to work only with attributes and not the element values.
I have also seen #XmlElementRef which lets determine the type based on the name of the element, but again, due to some restrictions all elements have to have the same name in input and output xml.
My input xml is:
<someRoot>
<type>chooseOne</type>
<message>Message for InheritingClassOne</message>
<mark>12.3</mark>
</someRoot>
I did not find solution for this problem other than using #XmlJavaAdapter with defined adapter:
public class CustomAdapter extends XmlAdapter<Object, BasicClass> {
#Override
public BasicClass unmarshal(Object v) throws Exception {
//TODO: cast v to Element interface, get to type element value and handle accordingly
return null;
}
#Override
public Object marshal(BasicClass v) throws Exception {
//TODO: marshal
return null;
}
}
The adapter solution with reading ElementNsImpl child values to get the category seems awful and takes a lot of effort for such task. Are there any solutions that I am missing? Can I somehow change my models (without using xml attributes), so this task is doable?
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 am using Spring #RequestBody to map a JSON payload to a Java Object. Unfortunately this JSON payload does not use a set convention but rather has names that use both camelCase and snake_case.
To be clear my Controller looks like this:
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
... code here ...
}
with the MobileDeviceData Entity object having several setter methods like:
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public void setFlagId(int flagId) {
this.flagId = flagId;
}
This works great and without any extra effort when the JSON objects name is camelCase. However for snake_case names I need to add the Annotation:
#JsonProperty("flag_id")
private int flagId;
in order for it to be picked up.
I know it's not a good idea to use the #JsonProperty if it can be avoided as you then will need to annotate every parameter. My question is, is there a more general way to enforce matching snake_case with the corresponding camelCase in the Entity object? And obviously to do it without screwing up the ones that are already camelCase.
As per the article here, there is a simple approach to deserialize the MobileDeviceData class. Here is the sample code as below:
#JsonDeserialize(using = UserDeserializer.class)
public class User {
private ObjectId id;
private String username;
private String password;
public User(ObjectId id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public ObjectId getId() { return id; }
public String getUsername() { return username; }
public String getPassword() { return password; }
}
Assume User is the class we’re interested in writing the Deserializer for. Not much is notable here, except for the annotations that tell Jackson who knows how deserialize this class.
public class UserDeserializer extends JsonDeserializer {
#Override
public User deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
return new User(null,
node.get("username").getTextValue(),
node.get("password").getTextValue());
}
}
The deserializer is created by extending Jackson’s abstract JsonDeserializer class, and giving it the type we want to deserialize to. Difficult is figuring out that you can reference the JSON by field name with the JsonParser's ObjectCodec.
I hope it helps.
Please feel free to comment if needed!
Having been working on this a bit, I now realize doing anything like what was requested would be counterproductive.
When you receive (deserialize) a JSON Object, it is generally expected that you will deliver (serialize) with the same parameters. If an implementation extracted both camelCase and underscore parameters the same way, then it would not know how to deserialize correctly later on. By following a standard convention and then using #JsonProperty for all the exceptions, it remains possible to deserialize and later deliver the JSON object just as it was received.
To marshal a long primitive type using JAXB, I have used #XmlJavaTypeAdapter annotation, which will adapt non-String type to a String. Even though it throw error for long type. Why is it so? How can I do marshalling on my long id attribute?
User.java
class User {
#XmlID
#XmlJavaTypeAdapter(WSLongAdapter.class)
private long id;
// Other variables
// Getter & Setter method
}
WSLongAdapter.java
public class WSLongAdapter extends XmlAdapter<String, Long>{
#Override
public String marshal(Long id) throws Exception {
if(id==null) return "" ;
return id.toString();
}
#Override
public Long unmarshal(String id) throws Exception {
return Long.parseLong(id);
}
}
MarshallTest.java
public static void main(String[] args) {
try{
JAXBContext jaxbContext= JAXBContext.newInstance(User.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
OutputStreamWriter writer = new OutputStreamWriter(System.out);
// Manually open the root element
writer.write("<user>");
// Marshal the objects out individually
marshaller.marshal(new User(), writer);
// Manually close the root element
writer.write("</user>");
writer.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
Error:
com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 4 counts of IllegalAnnotationExceptions
Adapter com.v4common.shared.util.other.WSLongAdapter is not applicable to the field type long.
this problem is related to the following location:
at #javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(type=class javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter$DEFAULT, value=class com.v4common.shared.util.other.WSLongAdapter)
at private long com.v4common.shared.beans.usermanagement.User.id
at com.v4common.shared.beans.usermanagement.User
Property "id" has an XmlID annotation but its type is not String.
this problem is related to the following location:
at private long com.v4common.shared.beans.usermanagement.User.id
at com.v4common.shared.beans.usermanagement.User
There are two properties named "id"
Specify the primitive class in the annotation:
#XmlJavaTypeAdapter(type=long.class, value=WSLongAdapter.class)
The following might work:
class User {
#XmlID
#XmlJavaTypeAdapter(WSLongAdapter.class)
#XmlElement(type=Long.class)
private long id;
// Other variables
// Getter & Setter method
}
Create new getter annotated with #XmlID that returns String
#XmlAccessorType(XmlAccessType.FIELD)
class User {
private long id;
#XmlID
public String getReferenceId() {
return Long.toString(id);
}
}
Only disadvantage of this solution is an additional tag is present in serialized XML (in this case <referenceId>). I tried to remove it by annotating that getter with #XmlTransient but then I got error informing that ID does not exists in referenced class.
Just replace
#XmlElement(type=Long.class)
private long id;
with
#XmlSchemaType(name = "long")
protected Long id;
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.