Use Enum in JPA - java

I have enum fields in DB in lowercase and as per Java standard, I have to define them in Uppercase in my spring boot application. If I am using lowercase, enums, I am able to get the results, but trying to convert DB lowercase value to uppercase using Spring attribute converter which is not working. Could someone tell me where I am doing wrong?
AccountLimitType.java
public enum AccountLimitType {
NONE,
HARD,
SOFT;
}
ConvertToDBAccountLimitType.java
#Converter(autoApply = true)
public class ConvertToDBAccountLimitType implements AttributeConverter<AccountLimitType, String>{
#Override
public String convertToDatabaseColumn(AccountLimitType attribute) {
return attribute.toString().toLowerCase();
}
#Override
public AccountLimitType convertToEntityAttribute(String dbData) {
return AccountLimitType.valueOf(dbData.toUpperCase());
}
}
Field in Entity class:
#NotNull
#Convert(converter = ConvertToDBAccountLimitType.class)
#Enumerated(EnumType.STRING)
#Column(name="limit_type",columnDefinition = "ENUM('none','hard','soft') default 'none'")
private AccountLimitType accountLimitType;
This is the response I am getting:
Response in postman

Can you try without #Enumerated(EnumType.STRING)
Also make sure that its calling your converter and don't forget to add a null check there

Related

SDN6 - Projection interfaces with Property Mapping

I am using spring data neo4j 6.1.3 and following is my use case code snippets
Domain Entity
#Data
#Node("DATspace")
public class DatSpace {
#Id #GeneratedValue
private Long neoId;
#Property("SUPtitle")
private String title;
private String SUPid;
}
Test class
#SpringBootTest
#EnableNeo4jRepositories(basePackages = "com.rahal.marvel")
public class ProjectionTest {
#Autowired
private Neo4jTemplate neo4jTemplate;
interface DATspaceProjection {
String getTitle();
String getSUPid();
}
#Test
public void test_projection(){
DatSpace d = neo4jTemplate.findOne("MATCH (s:DATspace {SUPid: $id}) RETURN s", Collections.singletonMap("id", "SPC_ML7"), DatSpace.class).get();
d.setTitle("title modified");
d.setSUPid("SUPid modified");
DATspaceProjection p = neo4jTemplate.saveAs(d, DATspaceProjection.class);
}
}
Ideally above saveAs function should modify both DATspace.SUPtitle and DATspace.SUPid. However it only modify SUPid but not SUPtitle. I presume it is due to property mapping (#Property) . Is this a bug or are there any workaround?
The provided #Property annotation does only have an impact on the annotated property (title) itself.
There is no knowledge right now that goes from the getTitle() method in the projection to the annotated title field in the domain class.
To be safe when modifying this use the explicit property name:
interface DATspaceProjection {
String getSUPtitle();
String getSUPid();
}
I created an issue for improvement https://github.com/spring-projects/spring-data-neo4j/issues/2371

Custom annotation not working with #JsonProperty

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.

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!

Create #ToLowerCase annotation to convert String value to lower case

We have SpringBoot application.
For our pojo's we want to create a custom #ToLowerCase annotation which converts the field variable value to lower case.
Eg:
#Data
Employee {
private String name;
#ToLowerCase
private String emailId;
private String gender;
private String phoneNumber;
}
So my custom #ToLowerCase annotation should convert emailId to lower case.
We want to use this annotation on all kind of Pojos, whether it is rest request pojo or JPA entity pojo.
I have gone through posts on many forums but didn't get any appropriate solution for same.
Is it possible to create such annotation in Spring Boot? If yes then how?
Kindly help
Thanks
Create a custom converter: ToLowerCaseConverter.
public class ToLowerCaseConverter extends StdConverter<String, String> {
#Override
public String convert(String value) {
if (value == null){
return null;
}
return value.toLowerCase();
}
}
After create a new annotation: ToLowerCase. It works for both incoming and outgoing Strings (#JsonDeserialize/#JsonSerialize).
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotationsInside
#JsonSerialize(converter = ToLowerCaseConverter.class)
#JsonDeserialize(converter = ToLowerCaseConverter.class)
public #interface ToLowerCase {
}
Finally, your example will work as intended:
#Data
Employee {
#ToLowerCase
private String emailId;
}

Using Spring #RequestBody to convert JSON with mixed convention in payload to Entity Object

I am using Spring #RequestBody to map a JSON payload to a Java Object. Unfortunately this JSON payload does not use a set convention but rather has names that use both camelCase and snake_case.
To be clear my Controller looks like this:
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
... code here ...
}
with the MobileDeviceData Entity object having several setter methods like:
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public void setFlagId(int flagId) {
this.flagId = flagId;
}
This works great and without any extra effort when the JSON objects name is camelCase. However for snake_case names I need to add the Annotation:
#JsonProperty("flag_id")
private int flagId;
in order for it to be picked up.
I know it's not a good idea to use the #JsonProperty if it can be avoided as you then will need to annotate every parameter. My question is, is there a more general way to enforce matching snake_case with the corresponding camelCase in the Entity object? And obviously to do it without screwing up the ones that are already camelCase.
As per the article here, there is a simple approach to deserialize the MobileDeviceData class. Here is the sample code as below:
#JsonDeserialize(using = UserDeserializer.class)
public class User {
private ObjectId id;
private String username;
private String password;
public User(ObjectId id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public ObjectId getId() { return id; }
public String getUsername() { return username; }
public String getPassword() { return password; }
}
Assume User is the class we’re interested in writing the Deserializer for. Not much is notable here, except for the annotations that tell Jackson who knows how deserialize this class.
public class UserDeserializer extends JsonDeserializer {
#Override
public User deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
return new User(null,
node.get("username").getTextValue(),
node.get("password").getTextValue());
}
}
The deserializer is created by extending Jackson’s abstract JsonDeserializer class, and giving it the type we want to deserialize to. Difficult is figuring out that you can reference the JSON by field name with the JsonParser's ObjectCodec.
I hope it helps.
Please feel free to comment if needed!
Having been working on this a bit, I now realize doing anything like what was requested would be counterproductive.
When you receive (deserialize) a JSON Object, it is generally expected that you will deliver (serialize) with the same parameters. If an implementation extracted both camelCase and underscore parameters the same way, then it would not know how to deserialize correctly later on. By following a standard convention and then using #JsonProperty for all the exceptions, it remains possible to deserialize and later deliver the JSON object just as it was received.

Categories