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;
Related
I have a very simple Entity (Person.java) that I am wanting to persist via JPA/Hibernate.
The Entity contains two fields: ID and Identification String.
The ID is a simple Integer, and is no problem. The Identification String is currently a String, but for various reasons, I want to instead use a wrapper class for String (IDString), where there are various validation methods among other things.
I am wondering how I can get JPA/Hibernate to use the wrapped string (inside the custom class IDString) when persisting the Person table in the database. I know this can probably be solved by letting the IDString be #Embeddable and then embed IDString in the Person entity with #Embedded, but I am looking for another method, mostly because IDString is in an entirely different package, and I am reluctant to have to go there and change stuff.
Googling, I found https://www.baeldung.com/hibernate-custom-types, but it seems to be mostly about more complicated cases, where you want to convert one class into another type, and I do feel that there is probably a smarter way that I am simply overlooking.
Here is the entity (in theory)
#Entity(name="Person")
#Table(name="DB_TABLE_PERSON")
public class Person implements Serializable {
#Id
Integer id;
// WHAT SHOULD I PUT HERE? I WANT TO SIMPLY USE THE STRING INSIDE IDSTRING AS THE FIELD TO PERSIST
IDString idString;
// getter and setter for ID.
public void getIdString() {
return idString.getValue();
}
public void setIdString(String in) {
idString.setValue(in);
}
}
And here is the class IDString (in theory):
public class IDString {
// I really want to be a POJO
private final String the_string;
public IdString(String input) {
if (isValid(input)) {
the_string = input;
} else {
throw new SomeCoolException("Invalid format of the ID String");
}
public boolean isValid(String input) {
// bunch of code to validate the input string
}
public String getValue() {
return the_string;
}
public void setValue(String input) {
if (isValid(input)) the_string = s;
else throw new SomeCoolException("Invalid format of the ID String");
}
I know that I could place the validation if the IDString inside the Entity, but the IDString will be used elsewhere (it's a general custom class), so I don't want to do that. Is there a simple way?
#Converter(autoApply=true) // autoApply is reasonable, if not use #Converter on field
public class IDStringConverter implements AttributeConverter<IDString,String> {
#Override
public String convertToDatabaseColumn(IDString attribute) {
return attribute != null ? attribute.getValue() : null;
}
#Override
public IDString convertToEntityAttribute(String dbData) {
return dbData != null ? new IDString(dbData) : null;
}
}
With this you should not need any other modifications in your code. One limitation of the AttributeConverter is that it maps from exactly 1 Java field to exactly 1 DB column. If you wanted to map to more columns (not the case here), you would need embeddables.
You could also put a #Column annotation on the getter:
#Entity
public class Person {
private final IdString idString = new IdString();
#Column(name = "ID_STRiNG")
public IdString getIdString() {
return idString.getValue();
}
public void setIdString(String input) {
idString.setValue(input);
}
Another solution could be to convert to/from IdString using #PostLoad and #PrePersit event handlers:
#Entity
public class Person {
#Column(name = "ID_STRiNG")
private String the_string; // no getters & setters
#Transient
private final IdString idString = new IdString();
#PostLoad
public void postLoad() {
idString.setValue(the_string);
}
#PrePersist
public void prePersist() {
the_string = idString.getValue();
}
// getters & setters for idString
I'm trying to retrieve documents from Mongo DB. The documents received well when the Gender attribute is not included. But when it's included an error occurs.
Here is my function to retrieve the person documents from Mongo
private List<Person> getPersons()
{
// Query to get person list
// Loop through person list
for (Person person : personList) {
// Some condition checking
filteredPersonList.add(person);
}
return filteredPersonList;
}
Here is my Person class
public class Person {
#BsonProperty("firstName")
#JSONField("firstName")
public String FirstName
#BsonProperty("lastName")
#JSONField("lastName")
public String LastName
#BsonProperty("gender")
#JSONField("gender")
public Gender Gender
#BsonProperty("isMarried")
#JSONField("isMarried")
public Boolean IsMarried
}
Here is the Gender enum for the Gender property
#JSONType(serializeEnumAsJavaBean = true)
public enum Gender {
Male(0),
Female(1),
;
private final int type;
Gender (int type) {
this.type = type;
}
public int getType() {
return type;
}
}
This is the error I get
org.bson.codecs.configuration.CodecConfigurationException: Failed to decode 'Person'. Decoding 'gender' errored with: readString can only be called when CurrentBSONType is STRING, not when CurrentBSONType is INT32.A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type.
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:224)
at org.bson.codecs.pojo.PojoCodecImpl.decodeProperties(PojoCodecImpl.java:197)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:121)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:125)
at org.bson.codecs.pojo.LazyPojoCodec.decode(LazyPojoCodec.java:57)
at org.bson.codecs.DecoderContext.decodeWithChildContext(DecoderContext.java:96)
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:218)
... 58 more
In Mongo DB, the type of the gender property is INT32.
You have to add custom codec following your mongodb implementation.
See this
http://mongodb.github.io/mongo-java-driver/4.3/bson/codecs/
I have set of objects of different types.
Ex : Employee emp, adress adr
These two classes have list of properties
public class Employee{
private Stringname;
private int age;
}
public class Adress {
private String HouseNo;
private string Street;
private string pin;
}
Each attribute is assigned with some 2 character value
Name (NA), age (AG), HouseNo(HN),Street(ST), pin(PN)
I need to construct a string with these data and delimit with a %
Output:
NA%Vidhya%AG%30%HN%80%ST%1st cross%PN%100100
Each class knows it own data best so I would let each class be responsible for generating the string. As I understand it the two char codes for each field are unique for each class and member and only used when generating the string so only the class would need them.
interface AttributeDescription {
String generateDescription();
}
public class Employee implements AttributeDescription {
//members...
public String generateDescription() {
return String.format(“NA%%%s%%AG%%%d”, name, age)
}
Then simply call this method for all objects implementing the interface.
AttributeDescription object = ...
String attr = object.generateDescription();
I don't think it can be generalized more than this given the requirements.
Update
It might be better to have a builder class for building the string to get a more unified behavior between classes. Here is an example
public class AttributeBuilder {
private builder = new StringBuilder();
public String getAttribute() {
return builder.toString();
}
public void add(String code, String value) {
if (value == null) {
return;
}
builder.append(code);
builder.append(‘%’);
builder.append(value);
builder.append(‘%’);
}
}
And then you would also have to implement add(...) methods for other data types in a similar fashion. The builder could then be used like
public String generateDescription() {
AttributeBuilder builder = new AttributeBuilder();
builder.add(“NA”, name);
builder.add(“AG”, age);
return builder.getAttribute();
}
I know it's possible to ignore fields if they are null or if they are empty, but is it possible to ignore a field, for example if it is a String, and contains a certain substring?
This is possible if you e.g. use a combination of #JsonIgnore and a Converter.
If you assume the following Person POJO:
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Person {
private final String email;
private final String name;
public Person(final String name, final String email) {
this.name = name;
this.email = email;
}
// Will use special conversion before serializing
#JsonSerialize(converter = EmailConverter.class)
public String getEmail() {
return email;
}
// Will simply use default serialization
public String getName() {
return name;
}
}
In the POJO you define that only non-empty values should be included. Furthermore, it is declared that a specific converter is to be used for the email property. The converter can be defined like this:
public class EmailConverter extends StdConverter<String, String> {
#Override
public String convert(final String value) {
return Optional.ofNullable(value)
.filter(email -> email.length() > 0)
.filter(email -> email.contains("#"))
.orElse(null);
}
}
Note that the converter uses Optional which is a java-8 feature but any validation code will do just fine. When null is returned it is simply skipped since it was declared that way in the Person class.
For more info, check out the JavaDocs for Converter and #JsonSerialize.
I need to implement custom conversion for ID field in Company and Employee classes. I have already implemented custom converter extended from StrutsTypeConverter and it is successfully used to convert Company.ID field, but it does not work for Employee.ID.
Seems like the main problem is in conversion properties file. How should I specify converter class for employee ID field in conversion properties file?
MyAction-conversion.properties:
company.id = com.struts2.convertors.MyCustomConverter
company.??????.id = com.struts2.convertors.MyCustomConverter
MyAction:
public class MyAction extends ActionSupport {
private Company company;
public Company getCompany () {
return company;
}
public void setCompany (Company company) {
this.company= company;
}
#Override
public String execute() {
return SUCCESS;
}
}
Company:
public class Company {
private ID id;
private List<Employee> employees;
// getters and setters
}
Employee
public class Employee{
private ID id;
private String name;
// getters and setters
}
TypeConversion Annotation:
This annotation is used for class and application wide conversion rules.
The TypeConversion annotation can be applied at property and method level.
#TypeConversion(converter = “com.test.struts2.MyConverter”)
public void setAmount(String amount)
{
this.amount = amount;
}
This annotation specifies the location of one of my converters. literally, by using this annotation, I register my class com.test.struts2.MyConverter as a converter, and gets executed every time when setAmount(String amount) method is invoked.
Try the following by adding a converter for the ID type to the xwork-conversion.properties file
com.struts2.ID = com.struts2.convertors.MyCustomConverter