I'm using JPA 2.0/Hibernate validation to validate my models. I now have a situation where the combination of two fields has to be validated:
public class MyModel {
public Integer getValue1() {
//...
}
public String getValue2() {
//...
}
}
The model is invalid if both getValue1() and getValue2() are null and valid otherwise.
How can I perform this kind of validation with JPA 2.0/Hibernate? With a simple #NotNull annotation both getters must be non-null to pass validation.
For multiple properties validation, you should use class-level constraints. From
Bean Validation Sneak Peek part II: custom constraints:
Class-level constraints
Some of you have expressed concerns
about the ability to apply a
constraint spanning multiple
properties, or to express constraint
which depend on several properties.
The classical example is address
validation. Addresses have intricate
rules:
a street name is somewhat standard and must certainly have a length limit
the zip code structure entirely depends on the country
the city can often be correlated to a zipcode and some error checking can
be done (provided that a validation
service is accessible)
because of these interdependencies a simple property level constraint does
to fit the bill
The solution offered by the Bean
Validation specification is two-fold:
it offers the ability to force a set of constraints to be applied before an
other set of constraints through the
use of groups and group sequences.
This subject will be covered in the
next blog entry
it allows to define class level constraints
Class level constraints are regular
constraints (annotation /
implementation duo) which apply on a
class rather than a property. Said
differently, class-level constraints
receive the object instance (rather
than the property value) in isValid.
#AddressAnnotation
public class Address {
#NotNull #Max(50) private String street1;
#Max(50) private String street2;
#Max(10) #NotNull private String zipCode;
#Max(20) #NotNull String city;
#NotNull private Country country;
...
}
#Constraint(validatedBy = MultiCountryAddressValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface AddressAnnotation {
String message() default "{error.address}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class MultiCountryAddressValidator implements ConstraintValidator<AddressAnnotation, Address> {
public void initialize(AddressAnnotation constraintAnnotation) {
// initialize the zipcode/city/country correlation service
}
/**
* Validate zipcode and city depending on the country
*/
public boolean isValid(Address object, ConstraintValidatorContext context) {
if (!(object instanceof Address)) {
throw new IllegalArgumentException("#AddressAnnotation only applies to Address objects");
}
Address address = (Address) object;
Country country = address.getCountry();
if (country.getISO2() == "FR") {
// check address.getZipCode() structure for France (5 numbers)
// check zipcode and city correlation (calling an external service?)
return isValid;
} else if (country.getISO2() == "GR") {
// check address.getZipCode() structure for Greece
// no zipcode / city correlation available at the moment
return isValid;
}
// ...
}
}
The advanced address validation rules
have been left out of the address
object and implemented by
MultiCountryAddressValidator. By
accessing the object instance, class
level constraints have a lot of
flexibility and can validate multiple
correlated properties. Note that
ordering is left out of the equation
here, we will come back to it in the
next post.
The expert group has discussed various
multiple properties support
approaches: we think the class level
constraint approach provides both
enough simplicity and flexibility
compared to other property level
approaches involving dependencies.
Your feedback is welcome.
To work properly with Bean Validation, the example provided in Pascal Thivent's answer could be rewritten as follows:
#ValidAddress
public class Address {
#NotNull
#Size(max = 50)
private String street1;
#Size(max = 50)
private String street2;
#NotNull
#Size(max = 10)
private String zipCode;
#NotNull
#Size(max = 20)
private String city;
#Valid
#NotNull
private Country country;
// Getters and setters
}
public class Country {
#NotNull
#Size(min = 2, max = 2)
private String iso2;
// Getters and setters
}
#Documented
#Target(TYPE)
#Retention(RUNTIME)
#Constraint(validatedBy = { MultiCountryAddressValidator.class })
public #interface ValidAddress {
String message() default "{com.example.validation.ValidAddress.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class MultiCountryAddressValidator
implements ConstraintValidator<ValidAddress, Address> {
public void initialize(ValidAddress constraintAnnotation) {
}
#Override
public boolean isValid(Address address,
ConstraintValidatorContext constraintValidatorContext) {
Country country = address.getCountry();
if (country == null || country.getIso2() == null || address.getZipCode() == null) {
return true;
}
switch (country.getIso2()) {
case "FR":
return // Check if address.getZipCode() is valid for France
case "GR":
return // Check if address.getZipCode() is valid for Greece
default:
return true;
}
}
}
You can use #javax.validation.constraints.AssertTrue validations like this:
public class MyModel {
private String value1;
private String value2;
#AssertTrue(message = "Values are invalid")
private boolean isValid() {
return value1 != null || value2 != null;
}
}
A custom class level validator is the way to go, when you want to stay with the Bean Validation specification, example here.
If you are happy to use a Hibernate Validator feature, you could use #ScriptAssert, which is provided since Validator-4.1.0.Final. Exceprt from its JavaDoc:
Script expressions can be written in any scripting or expression
language, for which a JSR 223 ("Scripting for the JavaTM Platform")
compatible engine can be found on the classpath.
Example:
#ScriptAssert(lang = "javascript", script = "_this.value1 != null || _this != value2)")
public class MyBean {
private String value1;
private String value2;
}
Programing Language : Java
This is a solution that helped me.
Requirement :
On UI there is a table which contains List of Objects which is having Maping to multiple Tables/Objects with fk relationship.
Now the validation is out of multiple fks there are only 3 columns which can't be duplicated. I mean the combination of 3 can't be duplicated.
Note : As I am working on Custom Framework on Java there is no option to use HashCode or equals. If I will use array index iteration that will increase the time complexity which I don't want.
Solution:
I have prepared a String , which is a custom String that contains ID of FK1#ID of FK2#ID of FK3
Ex: the String will form like -> 1000L#3000L#1300L#
This String, we will add to a set by using add() of set which will return false if duplicate comes up.
Based on this flag we can throw validation message.
This helped me. Some scenario and restriction comes where DS may not help.
Related
I've run into a situation where I need to validate a field inside an object conditionally. More specifically, I have one class PhoneType which contains two fields
#Getter
#Setter
public class PhoneType {
#JsonProperty("#CountryCode")
private String countryCode;
#JsonProperty("#Number")
private String number;
}
The class PhoneType is used in three places,
#Getter
#Setter
class PersonContact {
#JsonProperty("Mobile")
private PhoneType mobile;
#JsonProperty("WorkPhone")
private PhoneType workPhone;
#JsonProperty("OfficeFax")
private PhoneType officeFax;
}
However, with mobile, there should be an additional validation rule applied to the number field. The number must be a number with length of 10.
I have two possible solutions in mind:
Create a custom annotation to validate number for mobile
Validate number using Jackson's StdConverter
Here are the implementation of both solutions
public class ContactConverter extends StdConverter<PersonContact, PersonContact> {
#SneakyThrows
#Override
public PersonContact convert(PersonContact personContact) {
boolean validMobilePhone = Pattern.compile("\\d{10}")
.matcher(relatedPersonContact.getMobileNumber())
.matches();
if (BooleanUtils.isFalse(validMobilePhone)) {
var errorMessage = String.format(INVALID_MOBILE_NUMBER, personContact.getMobileNumber());
throw new JsonParseException(null, errorMessage);
}
return personContact;
}
}
Converter is used like this
#Getter
#Setter
#JsonDeserialize(converter = ContactConverter.class)
public class PersonContact {
#JsonProperty("#Email")
private String email;
#JsonProperty("WorkPhone")
private PhoneType workPhone;
#JsonProperty("Mobile")
private PhoneType mobile;
}
This is the code for custom annotation, however it's not working
#Retention(RUNTIME)
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Constraint(validatedBy = MobilePhoneNumberValidator.class)
#interface Phone {
String format() default "";
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
static class MobilePhoneNumberValidator implements ConstraintValidator<Phone, PhoneType> {
#Override
public void initialize(Phone constraintAnnotation) {
System.out.println("test");
}
#Override
public boolean isValid(PhoneType phoneType, ConstraintValidatorContext context) {
System.out.println("test again");
return false;
}
}
and this is how I use it
#Phone(format = "\\d{10}")
#JsonProperty("Mobile")
private PhoneType mobile;
However, the code inside the Validator is not executed.
I wonder if there is anything wrong with the custom annotation. This is SpringBoot 2.3.0, I can't think of any other reason why the custom annotation is not working.
Please help if you know there is a legit way handling dynamic annotation in Java, or you know why the above code isn't working, or you know a legit way of validating object's field just by name.
EDIT 1:
It seems like due to my poor way of explanation, there is misunderstanding.
https://www.baeldung.com/javax-validation-groups , this doesn't work in this case, the validation is applied only with the declaration of the PhoneType property in other classes (PersonContact)
I have two possible solutions, custom annotation and Jackson's converter.
I have successfully applied the converter but couldn't make the custom annotation work.
My custom annotation should be run after #JsonProperty, because it needs to have the field PhoneType mobile number to be deserialized.
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 have an entity class with the following unique constraint setup:
#Table(name = "foo", uniqueConstraints = {
#UniqueConstraint(columnNames = {"service", "system", "priority", "is_default"})})
Where service and system are foreign keys for other entity classes, priority is an integer holding the priority of the entry among entries with the same service and system objects and is_default is a boolean indicating a default configuration entry.
This unique constraint almost does what I want to do, but what I would need is a setup where, if is_default is FALSE then there can be multiple entries with the same service and system keys just with different integer priority, while if is_default is TRUE then there can be only 1 entry with the given service and system keys, meaning there can be only 1 default entry for a given service and system. How could I achive such a constraint?
The key is to create your own validation annotation able to validate whether the row data is unique based on a particular criteria.
Let the service iterface extend UniqueValidated and implement the method that performs the validation
public interface UniqueValidated {
boolean isUnique(Object value, String fieldName) throws UnsupportedOperationException;
}
public interface FooService extends UniqueValidated {
// add, delete...
}
public class FooServiceImpl implements FooService {
// add, delete...
#Override
public boolean isUnique(Object value, String fieldName)
throws UnsupportedOperationException {
// the logic of validation itself, feel free to use DAO implementations
}
}
Create the annotation that you put over the mapped attribute.
#Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE
})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueValidator.class)
#Documented
public #interface Unique {
String message() default "{validation.unique}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends UniqueValidated> service();
String serviceQualifier() default "";
String fieldName();
}
Finally create the class handling with the annotation.
public class UniqueValidator implements ConstraintValidator<Unique, Object> {
#Autowired
private ApplicationContext applicationContext;
private UniqueValidated service;
private String fieldName;
#Override
public void initialize(Unique unique) {
Class<? extends UniqueValidated> clazz = unique.service();
this.fieldName = unique.fieldName();
this.service = this.applicationContext.getBean(clazz);
}
#Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
if (this.service == null || this.service.isUnique(o, this.fieldName)) {
constraintValidatorContext.disableDefaultConstraintViolation();
return true;
}
return false;
}
}
I have got inspired with the tutorial available online on the JBoss Docs. This is quite complex structure, however it leads the result well. The biggest advantage is undoubtly you can make the custom unique validation over any service implementing the UniqueValidated. Anyway, you need to these snippets above customize for your project.
Mapping is simple:
#Unique(service = FooService.class, fieldName = "theNameOfThisField"
#Column(name = "...")
private String theNameOfThisField;
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 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" );
(...)