Make hibernate call a different method than valueof() when persisting enums - java

I'm trying to retrieve an enum from a database and Hibernate doesn't find the property value and therefore throws the following Exception java.lang.IllegalArgumentException: No enum constant com.intrawa.qx.validator.models.entity.Group.CONFIGTAB.false
The problem occurs when hibernate calls the valueof method of the enum: at java.lang.Enum.valueOf(Enum.java:238)
I'm working with a Database where I cannot change it's column names.
For example look at the code:
#Entity
#Data
#Table(name = "bm_host_groups")
public class Group {
#Enumerated(EnumType.STRING)
private CONFIGTAB configTab;
#AllArgsConstructor
private enum CONFIGTAB {
TRUE("true"),
FALSE("false");
#Getter #Setter
private String value;
}
}
The DB has a column called configTab with enum value type and two possible values ("true", "false"), when hibernate call valueof("true") it doesn't find TRUE and throws the exception.
One solution I found it's on this post: What is the reason for java.lang.IllegalArgumentException: No enum const class even though iterating through values() works just fine?
However, it needs a custom method that takes for example "true" as argument from the DB then capitallize it in order to find the value of the enum.
I searched a way to override Enum's valueof() but this post says it's not possible and basically suggest the same as the first solution, make a custom method and make the client calls it. Post: Override valueof() and toString() in Java enum
Is there a way to make hibernate call a custom method instead of valueof()?

It is not possible to override valueOf(). But it is possible to manipulate the value that valueOf() will get. Namely, the correct solution is to use AttributConverter and discard the #Enumerated annotation, like:
#Getter #Setter
//#Enumerated(EnumType.STRING)
#Convert(converter=ConfigTabConverter.class)
private CONFIGTAB configTab;
Converter is quite simple:
#Converter(autoApply=true)
public class ConfigTabConverter
implements AttributeConverter<CONFIGTAB,, String> {
#Override
public String convertToDatabaseColumn(CONFIGTAB attribute) {
return attribute.getValue();
}
#Override
public CONFIGTAB convertToEntityAttribute(String dbData) {
return CONFIGTAB.valueOf(dbData.toUpperCase());
}
}

Related

Jackson deserialization behaviour for a getter without property

While using jackson to deserialize and serialize a java DTO came across a weird behaviour.
Consider this sample java dto,
public class TempClz {
private List<String> field1;
public List<String> getField1() {
return field1;
}
public void setField1(List<String> field1) {
this.field1 = field1;
}
public List<String> getNotAtAll() {
return field1;
}
}
Now on creating a object of this class with field1 set to some value and on serializing this via a standard jackson mapper the sample json string obtained was
{"field1":["123"],"notAtAll":["123"]}
This behaviour was weird and I did not get a direct explanation for this in docs, but that once a getter is made, the property is available for both serialization and deserialization. This created a property using the function name in the resultant json response.
But again on deserialization of this string to pojo, the notAtAll list got appended to field1 list, i.e. in the resultant java object, field1 had size of two with values ["123","123"] which just seems wrong. Fixed this behaviour using jsonIgnore on the getter but can someone please help explain this behaviour exactly and whether this is intended?
A Getter Makes a Non-Public Field Serializable and Deserializable. So, no doubt that it added notAtAll field in json for getNotAtAll method. When you de-serialize the same string it has the values from both the getters but both of them return the same field i.e. field1. And consequently the values are added to your list. You've rightly used #JsonIgnore to ignore this getter.

JSON serialization of different attributes of a single entity for different requests using Jackson

There are several REST calls that require the same JSON entity with a different set of attributes. Example of the entity:
public class JsonEntity
{
public String id;
public String name;
public String type;
public String model;
}
JsonEntity is a part of the complex responses of different calls. The first call requires the whole JsonEntity without changes. Second call requires JsonEntity without type and model attributes. Thrid one requires JsonEntity without name attribute.
Is there any way to retrieve the same JSON entity with a particular set of attributes depending on the particular context (except separating JsonEntity) using Jackson?
I see 3 ways of doing this:
1. Use #JsonGetter
This annotation tells jackson to use a metho rather than a field for serialization.
Create 3 subclasses of JsonEntity, one for each response. Change JsonEntity and use #IgnoreField on every field, make them protected if possible. On each subclasses, create specific getter for the fields you need, example:
public class JsonEntitySecondCall extends JsonEntity
{
#JsonGetter("id")
public String getId(){
return id;
}
#JsonGetter("name")
public String getName(){
return name;
}
}
Also, create a clone/copy constructor for JsonEntity. For your second call, create a new JsonEntitySecondCall by cloning the original JsonEntity, and use it in your API. Because of the anotation, the created Object will only serialisze the given fields. I don't this you can just cast your object, because Jackson uses reflection.
2. Use #AnyGetter
the AnyGetter annotaiton allows you to define a map of what will be serialized:
private Map<String, Object> properties = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> properties() {
return properties;
}
Now you just need to tell your JsonEntity what properties it needs to return before each call (you could create 3 methods, one for each context, and use an enum to set which one must be used.).
3. Use #JsonInclude(Include.NON_NULL)
This annotation tells Jackson not to serialize a field if it is null. You can then clone your object and set null the fields you don't want to send. (this only works if you shouldn't send null elements to the API)
For more on Jackson annotations use this link.

AWS DynamoDBMapper save method keeps throwing `DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted`

I followed everything that is outlined here - https://github.com/derjust/spring-data-dynamodb/wiki/Use-Hash-Range-keys. But still no luck.
I have a DynamoDB table with a hash key and a sort key.
Here is my entity class RecentlyPlayed.class
#DynamoDBTable(tableName="some-table")
public class RecentlyPlayed {
#Id
private RecentlyPlayedId recentlyPlayedId;
// ----- Constructor methods -----
#DynamoDBHashKey(attributeName="keyA")
// Getter and setter
#DynamoDBRangeKey(attributeName="keyB")
// Getter and setter
}
Here is my key class RecentlyPlayedId.class
public class RecentlyPlayedId implements Serializable {
private static final long serialVersionUID = 1L;
private String keyA;
private String keyB;
public RecentlyPlayedId(String keyA, String keyB) {
this.keyA = keyA;
this.keyB = keyB;
}
#DynamoDBHashKey
// Getter and setter
#DynamoDBRangeKey
// Getter and setter
}
Here is my repository interface RecentlyPlayedRepository
#EnableScan
public interface RecentlyPlayedRepository extends CrudRepository<RecentlyPlayed, RecentlyPlayedId> {
List<RecentlyPlayed> findAllByKeyA(#Param("keyA") String keyA);
// Finding the entry for keyA with highest keyB
RecentlyPlayed findTop1ByKeyAOrderByKeyBDesc(#Param("keyA") String keyA);
}
I am trying to save an object like this
RecentlyPlayed rp = new RecentlyPlayed(...);
dynamoDBMapper.save(rp); // Throws that error
recentlyPlayedRepository.save(rp); // Also throws the same error
I am using Spring v2.0.1.RELEASE. The wiki in the original docs warns about this error and describes what to do to mitigate. I did exactly what they said. But still no luck.
The link to that wiki is here - https://github.com/derjust/spring-data-dynamodb/wiki/Use-Hash-Range-keys
DynamoDB only supports primitive data types, it does not know how to convert your complex field (recentlyPlayedId) into a primitive, such as a String.
To show that this is the case, you can add the annotation #DynamoDBIgnore to your recentlyPlayedId attribute like this:
#DynamoDBIgnore
private RecentlyPlayedId recentlyPlayedId;
You also need to remove the #id annotation.
Your save function will then work, but the recentlyPlayedId will not be stored in the item. If you do want to save this field, you need to use the #DynamoDBTypeConverted annotation and write a converter class. The converter class defines how to convert the complex field into a String, and then uncovert the String into the complex field.
Removing getters/setters for the #Id field fixed the problem for me. This is suggested in https://github.com/derjust/spring-data-dynamodb/wiki/Use-Hash-Range-keys
not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted",
i was getting this error when i defined model class with field JsonNode,i converted it to MAP<String,String>,now it is working fine

Spring/JPA/persistence entity attribute field cannot be final?

I have a Spring MVC project using JPA which I have worked on for some time in the past without this issue. But now for some reason (likely an environmental issue as I have switch to a new laptop since I last worked on it) I am getting this weird error.
The project is essentially a tool for creating and performing surveys which are just a set of questions. There are multiple types of question such as "auto complete question", "multiple choice question", "integer question", etc which collect different types of data. Each of this question types is modeled by a subclass which extends an abstract class called DdmQuestion which looks something like this:
#Entity
#Table(name = "ddm_question")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING, name = "question_type")
#JsonIgnoreProperties({"dataType"})
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#question_type")
#JsonSubTypes(value = { #Type(DdmTextQuestion.class),#Type(DdmDateQuestion.class),#Type(DdmTimeQuestion.class),#Type(DdmNumberIntegerQuestion.class),#Type(DdmChoiceMultiQuestion.class),#Type(DdmAutoCompleteQuestion.class) })
public abstract class DdmQuestion {
#Id
#GeneratedValue
#Column(name = "question_id")
private int questionId;
#Column(name = "name")
private String name;
public int getQuestionId() {
return questionId;
}
public void setQuestionId(int questionId) {
this.questionId = questionId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonIgnore
public abstract String getDataType();
}
Note the getDataType() method.
Then, for each question type, I have a subclass extending this which looks something like this:
#Entity
#DiscriminatorValue("ddm_question_date")
public class DdmDateQuestion extends DdmQuestion {
final private String DATA_TYPE = "Long"; // this is the line with the error
#Override
public String getDataType() {
return DATA_TYPE;
}
}
Now, I've never encountered this error before (that I can recall) but Eclipse is throwing up an error here that says:
"The Java field for attribute "DATA_TYPE" is final". That's all it
says.
If I remove the #Entity annotation from the class, this error disappears so evidently something in JPA doesn't like something about this but I never had this error before so I'm thinking something changed in a newer version. My POM is not particularly explicit with dependency versions so this would not be surprising.
Can anyone explain to me why this is happening and what the correct resolution is? I could just remove the "final" from the field declaration but this seems wrong to me as it is definitely a final value...
Thanks in advance.
If it is a field that should not be persisted in the database you usually should take advantage of the transient annotation which would tell the persistence provider to ommit that field in its processing.:
#Transient
final private String DATA_TYPE = "Long";
If Eclipse is smart enough, it should stop highlighting the error altogether.
in this linkshttp://docs.oracle.com/javaee/5/tutorial/doc/bnbqa.html#Entities;
An entity class must follow these requirements:
The class must be annotated with the javax.persistence.Entity annotation.
The class must have a public or protected, no-argument constructor. The class may have other constructors.
The class must not be declared final. No methods or persistent instance variables must be declared final.
If an entity instance be passed by value as a detached object, such as through a session bean’s remote business interface, the class must implement the Serializable interface.
Entities may extend both entity and non-entity classes, and non-entity classes may extend entity classes.
Persistent instance variables must be declared private, protected, or package-private, and can only be accessed directly by the entity class’s methods. Clients must access the entity’s state through accessor or business methods.

Modifying property names in JPA queries

I'm using a convention of prefixing field names with an underscore. When I generate annotate entity classes with such fields I am stuck to using the underscore-prefixed property names in queries. I want to avoid that, and be able to do:
#Entity
public class Container {
private String _value;
}
// in a lookup method
executeQuery("from Container where value = ?", value);
Is that possible with JPA in general or Hibernate in particular?
Update: Still trying to remember why, but I need this to be annotated on fields rather than on getters.
You can annotate the getter:
#Entity
public class Container {
private String _value;
#Column
public String getValue()
{
return _value;
}
public void setValue( String value )
{
this._value = value;
}
}
You could perhaps write subclasses of your generated entity classes, which have getter methods on them, and then configure the entity manager to use getter/setter access instead if field access? Then your getters/setters could have any name you liked.
Have a look at NamingStrategy. It would be fairly easy to extend the DefaultNamingStrategy and override the columnName method to strip the first underscore (if it is there).

Categories

Resources