Mapping an Enum with JPA - java

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() {...}

Related

Adding enum values to database [duplicate]

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.

How from Entity to DTO if Entity has an Enum variable?

I'm creating DTO versions of all my entities. I have a problem with an entity that has one Enum value. This is my entity:
#Getter
#Setter
#Table(name = "TIPOS_MOVIMIENTO")
#Entity
public class TipoMovimiento {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
#Convert(converter = TipoMovEnumConverter.class)
private TipoMov tipo;
public String getTipo() {
return tipo.getTipoNombre();
}
#OneToMany(mappedBy = "tipoMov")
private List<Movimiento> movimientos;
No, I don't have #Enumerated because I followed a tutorial: "JPA 2.1 Attribute Converter – The better way to persist enums" and I had to remove it. I use a converter, as you can see.
This is my DTO of the previous entity:
#Getter
public class TipoMovimientoDto implements DtoEntity {
#Convert(converter = TipoMovEnumConverter.class) //I don't even know if write this here!!!!!
private TipoMov tipo;
}
The reason why I've followed that tutorial ↑ is because I wanted to write in database the variable values (tipoNombre) of enum (not enum name itself) because format. I want to store it in DB with accents, and I want to show it in Postman or whatever REST Client app with accents! Don't tell me anything about format it in front-end because this project is only back-end :(
Well, I think you will understand what I found with this with a image:
If you know a better way to do this, let me know, but this is not my problem now.
Let me show you the Enum:
public enum TipoMov {
INGRESO("Ingreso"),
PRESTAMO("Prestamo"),
PAGO("Pago"),
AMORTIZACION("Amortización"),
INTERES("Interés");
private String tipoNombre;
public String getTipoNombre() {
return tipoNombre;
}
TipoMov(String tipoNombre) {
this.tipoNombre = tipoNombre;
}
public static TipoMov fromDBName(String tipoNombre) {
switch (tipoNombre) {
case "Ingreso":
return TipoMov.INGRESO;
case "Préstamo":
return TipoMov.PRESTAMO;
case "Pago":
return TipoMov.PAGO;
case "Amortización":
return TipoMov.AMORTIZACION;
case "Interés":
return TipoMov.INTERES;
default:
throw new IllegalArgumentException("ShortName [" + tipoNombre
+ "] not supported.");
}
}
}
The problem is that I can't get the output in Postman if I convert this to DTO version. I get the appropiate output without DTO. I'm using REST services. Let me show you the services and controller.
(They include both versions, without DTO and with DTO (that is not working)).
ServiceImpl
#Service
public class TipoMovimientoServiceImpl implements TipoMovimientoService {
#Autowired
TipoMovimientoRepository repository;
#Autowired
DtoUtils dtoUtils;
public List<DtoEntity> findAllDto() {
List<TipoMovimiento> tiposMovimiento = repository.findAll();
List<DtoEntity> tiposMovimientoDto = new ArrayList();
for (TipoMovimiento tipoMovimiento : tiposMovimiento) {
DtoEntity tipoMovimientoDto= dtoUtils.convertToDto(tipoMovimiento, new TipoMovimientoDto());
tiposMovimientoDto.add(tipoMovimientoDto);
}
return tiposMovimientoDto;
}
public List<TipoMovimiento> findAll() {
List<TipoMovimiento> tiposMovimiento = repository.findAll();
return tiposMovimiento;
}
}
Service Interface
public interface TipoMovimientoService {
List<DtoEntity> findAllDto();
List<TipoMovimiento> findAll();
}
Controller:
#RestController
public class PruebasController {
#Autowired
TipoMovimientoService service;
#GetMapping("tiposmovdto")
public ResponseEntity <List <DtoEntity> > findAllDto() {
return ResponseEntity.ok(service.findAllDto());
}
#GetMapping("tiposmov")
public ResponseEntity <List <TipoMovimiento> > findAll() {
return ResponseEntity.ok(service.findAll());
}
}
As I said, the nonDto version works perfectly, but DTO version no. Is not the fault of DTO converter, because I have other REST services (that don't have enums) working perfectly with DTO. This is a problem about making compatible Enum and Dto!
I got it!!! I never thought this would work.
#Getter
public class TipoMovimientoDto implements DtoEntity {
private TipoMov tipo;
}
I just changed in the code above (Dto):
private TipoMov tipo;
to
private String tipo;
I can't explain how Enum from Entity could have been converted to DTO, using String instead Enum... But that worked!
In case you have the same problem... this is my Attribute Converter between Enum and String
#Converter(autoApply = true)
public class TipoMovEnumConverter implements AttributeConverter<TipoMov, String> {
public String convertToDatabaseColumn(TipoMov tipoMov) {
return tipoMov.getTipoNombre();
}
public TipoMov convertToEntityAttribute(String dbData) {
return dbData == null ? null : TipoMov.fromDBName(dbData);
}
}
Is still necessary to use it in Entity class, above of the enum variable:
#Convert(converter = TipoMovEnumConverter.class)
But not necessary in DTO. Just use String instead Enum in DTO!

JPA retrieve enum with white space

I have a spring boot application and I have defined some POJO classess to specify the data model. In a table I want to store enum columns but I have an enum data type and some of the enums contains spaces.
enum DataEnum {
Workload("Workload"),
ReleaseContent("Release content"),
"VerificationProc"("Verification proc")
private String name = "";
DataEnum (final String name) {
this.name = name;
}
public String getName() {
return name;
}
public DataEnum fromString(String value) {
return DataEnum.valueOf(value);
}
#Override
public String toString() {
return name;
}
}
I have a POJO entity class as below:
#Entity
#IdClass(FieldId.class)
public class Field {
#Id
private String id;
#Id
#Enumerated(EnumType.STRING)
private DataEnum sheet;
...
}
When I try to retrieve data from the database I get:
java.lang.IllegalArgumentException: Unknown name value [Release
content] for enum class [data.util.DataEnum]
JPA cannot map your string to enum because it cannot find any enum with that name exists.
Internally, the #Enumerated use the method Enum.valueOf to convert the DB String to enum constant. The DB String required to be exactly the same to the enum constant identifier.
That is, if you want to store DataEnum.ReleaseContent, you have to store it as ReleaseContent not Release content.
In your case, if you want to store value different than your enum identifier, you can declare a custom converter:
#Converter
public class DataEnumConverter implements AttributeConverter<DataEnum, String> {
#Override
public String convertToDatabaseColumn(DataEnum enum) {
// Convert your enum to DB value
}
#Override
public DataEnum convertToEntityAttribute(String dbValue) {
// Convert String to your enum
}
}
You can see My Answer for an example how to convert String to enum
And in your entity:
#Column
#Convert(converter = DataEnumConverter.class)
private DataEnum sheet;

Unrecognized Hibernate Type when using enum in query

I have an Entity class with a set of enums:
#Entity
public class Something {
public enum Type{
FIRST_ENUM((short)1),
SECOND_ENUM((short)2);
private short id;
private Type(short id) {
this.id = id;
}
public short getId() {
return id;
}
}
#CollectionTable(name="table_name", joinColumns=#JoinColumn(name="something_id"))
#Column(name="type")
private Set<Type> types;
... //other props + getters and setters
}
For the enum I made a converter (whole converter package is loaded by #EntityScan annotation) :
#Converter(autoApply=true)
public class TypeConverter implements AttributeConverter<Type, Integer> {
#Override
public Integer convertToDatabaseColumn(Type st) {
//implementation
}
#Override
public Type convertToEntityAttribute(Integer i) {
//implementation
}
}
Now when I try to use the enum in a query
... AND {packagename}.Something$Type.FIRST_ENUM MEMBER OF {someobject}.something.types ...
I stumble upon following error:
org.hibernate.QueryException: Unrecognized Hibernate Type for handling query constant ({package}.Something$Type.FIRST_ENUM); expecting LiteralType implementation or AttributeConverter
Does anyone got a clue why I cannot use the enum in my query? It somehow seems the enum is not known to Hibernate. I don't understand why, because the class is loaded when I start my application.

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, 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.

Categories