How to use Mapstruct with Ad Hoc Setters - java

I am trying to apply MapStruct to DDD. I created my entities with ad hoc setters like this:
#AllArgsContructor
#NoArgsConstructor //necessary to MapStruct
#Getter
class Employee {
private String id;
private String name;
public void updateName(String newName) {
this.name = newName;
}
}
and I have a EmployeeDto to send data to frontend API:
#Getter
#AlArgsConstructor
#NoArgsConstructor //necessary to MapStruct
class EmployeeDto {
private String name;
private String id;
}
so, I am trying to use MapStruct to map, but it is not working because my ad hoc setter. How could I resolve this problem?

You have to implement custom AccessorNamingStrategy. The way how to do it is well documented in section 13.1. Custom Accessor Naming Strategy of a MapStruct documentation.
In your case it should look something like this:
public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy {
#Override
public boolean isSetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return methodName.startsWith("update") && methodName.length() > 6;
}
#Override
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
if (isSetterMethod(getterOrSetterMethod)) {
String methodName = getterOrSetterMethod.getSimpleName().toString();
return IntrospectorUtils.decapitalize(methodName.substring(6));
} else {
return super.getPropertyName(getterOrSetterMethod);
}
}
}

Related

When does Jackson require no-arg constructor for deserialization?

In my spring boot project, I noticed a strange Jackson behavior. I searched over internet, found out what to do, but haven't found out why.
UserDto:
#Setter
#Getter
#AllArgsConstructor
public class UserDto {
private String username;
private String email;
private String password;
private String name;
private String surname;
private UserStatus status;
private byte[] avatar;
private ZonedDateTime created_at;
}
Adding a new user works just fine.
TagDto:
#Setter
#Getter
#AllArgsConstructor
public class TagDto {
private String tag;
}
Trying to add a new tag ends with an error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of TagDto (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
The solution to the problem was to add zero-arg constructor to the TagDto class.
Why does Jackson require no-arg constructor for deserialization in TagDto, while working just fine with UserDto?
Used same method for adding both.
My Tag and User entities are both annotated with
#Entity
#Setter
#Getter
#NoArgsConstructor
and have all args constructors:
#Entity
#Setter
#Getter
#NoArgsConstructor
public class User extends AbstractModel {
private String username;
private String password;
private String email;
private String name;
private String surname;
private UserStatus status;
#Lob
private byte[] avatar;
#Setter(AccessLevel.NONE)
private ZonedDateTime created_at;
public User(final String username, final String password, final String email, final String name, final String surname) {
this.username = username;
this.password = password;
this.email = email;
this.name = name;
this.surname = surname;
this.created_at = ZonedDateTime.now();
}
}
#Entity
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class Tag extends AbstractModel {
private String tag;
}
#MappedSuperclass
#Getter
public abstract class AbstractModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
}
Entity generation:
#PostMapping(path = "/add")
public ResponseEntity<String> add(#Valid #RequestBody final D dto) {
this.abstractModelService.add(dto);
return new ResponseEntity<>("Success", HttpStatus.CREATED);
}
public void add(final D dto) {
//CRUD repository save method
this.modelRepositoryInterface.save(this.getModelFromDto(dto));
}
#Override
protected Tag getModelFromDto(final TagDto tagDto) {
return new Tag(tagDto.getTag());
}
#Override
protected User getModelFromDto(final UserDto userDto) {
return new User(userDto.getUsername(), userDto.getPassword(), userDto.getEmail(), userDto.getName(), userDto.getSurname());
}
Error occurs when parsing JSON
{"tag":"example"}
sent via postman localhost:8081/tag/add, returns
{
"timestamp": "2020-09-26T18:50:39.974+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/tag/add"
}
I am using Lombok v1.18.12 and Spring boot 2.3.3.RELEASE with Jackson v2.11.2.
TL;DR: Solution is at the end.
Jackson supports multiple ways of creating POJOs. The following lists the most common ways, but it likely not a complete list:
Create instance using no-arg constructor, then call setter methods to assign property values.
public class Foo {
private int id;
public int getId() { return this.id; }
#JsonProperty
public void setId(int id) { this.id = id; }
}
Specifying #JsonProperty is optional, but can be used to fine-tune the mappings, together with annotations like #JsonIgnore, #JsonAnyGetter, ...
Create instance using constructor with arguments.
public class Foo {
private int id;
#JsonCreator
public Foo(#JsonProperty("id") int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Specifying #JsonCreator for the constructor is optional, but I believe it is required if there is more than one constructor. Specifying #JsonProperty for the parameters is optional, but is required for naming the properties if the parameter names are not included in the class file (-parameters compiler option).
The parameters imply that the properties are required. Optional properties can be set using setter methods.
Create instance using factory method.
public class Foo {
private int id;
#JsonCreator
public static Foo create(#JsonProperty("id") int id) {
return new Foo(id);
}
private Foo(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Create instance from text value using String constructor.
public class Foo {
private int id;
#JsonCreator
public Foo(String str) {
this.id = Integer.parseInt(id);
}
public int getId() {
return this.id;
}
#JsonValue
public String asJsonValue() {
return Integer.toString(this.id);
}
}
This is useful when a the POJO has a simply text representation, e.g. a LocalDate is a POJO with 3 properties (year, month, dayOfMonth), but is generally best serialized as a single string (yyyy-MM-dd format). #JsonValue identifies the method to be used during serialization, and #JsonCreator identifies the constructor/factory-method to be used during deserialization.
Note: This can also be used for single-value construction using JSON values other than String, but that is very rare.
Ok, that was the background information. What is happening for the examples in the question, it that UserDto works because there is only one constructor (so #JsonCreator is not needed), and many arguments (so #JsonProperty is not needed).
However, for TagDto there is only a single-argument constructor without any annotations, so Jackson classifies that constructor as a type #4 (from my list above), not a type #2.
Which means that it is expecting the POJO to be a value-class, where the JSON for the enclosing object would be { ..., "tag": "value", ... }, not { ..., "tag": {"tag": "example"}, ... }.
To resolve the issue, you need to tell Jackson that the constructor is a property initializing constructor (#2), not a value-type constructor (#4), by specifying #JsonProperty on the constructor argument.
This means that you cannot have Lombok create the constructor for you:
#Setter
#Getter
public class TagDto {
private String tag;
public TagDto(#JsonProperty("tag") String tag) {
this.tag = tag;
}
}

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!

convert a DTO to Entity with using mapper class

I have a Entity class something like this:
#Entity
public class Website {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String url;
public Website() {
//Constructor
//getters and setters
}
here is the DTO class:
public class WebsiteDto {
private Integer id;
private String name;
private String url;
public WebsiteVo() {
//Constructor
//getters and setters
}
I have the WebsiteMapper something like this:
#Component
public class WebsiteMapper {
public List<WebsiteDto> getWebsiteList() {
return repository.findAll().stream().map(w -> {
WebsiteDto dto = new WebsiteVo(w.getId(), w.getName(), w.getUrl());
return dto;
}).collect(Collectors.toList());
I also have Repository Interface:
public interface WebsiteRepository extends JpaRepository<Website, Integer> {
}
I want now to convert DTO to entity using my class WebsiteMapper. Because I did the conversion in this class. How I can do it?
How about using BeanUtils provided by spring org.springframework.beans.BeanUtils, something like this
public List<WebsiteDto> getWebsiteList() {
return repository.findAll().stream().map(w -> {
WebsiteDto dto = new WebsiteVo();
BeanUtils.copyProperties(w, dto); // copys all variables with same name and type
return dto;
})
.collect(Collectors.toList());
}
Hi I guess you wish to converting your entity to DTO. It's quite simple. Create static methods in your DTO class or any util class. The return type should be your DTO type.
e.g.
public class WebsiteDto {
private Integer id;
private String name;
private String url;
public static WebsiteDto export(Website website) {
// Return a new instance of your website DTO
return new WebsiteDto(
website.getId(),
website.getName(),
website.getUrl()
);
}
public static List<WebsiteDto> export(List<Website> websites) {
// Return a new instance of your website DTO list
return websites.stream().map(website -> {
return new WebsiteDto(
website.getName(),
website.getUrl()
}).collect(Collectors.toList());
}
}
NOTE You can also convert your DTO to entity using similar method.

hibernate - Persisting a composition interface of strategy pattern

I have the following class structure:
public abstract class Creature{
private String name;
//strategy pattern composition
private SkillInterface skill;
}
public interface SkillInterface {
void attack();
}
public class NoSkill implements SkillInterface {
#Override
public void attack() {
//statements
}
}
My goal is to persist Creature objects at one table in database. Subclasses of SkillInterface are without any fields. As they determine the behaviour, I want to convert selected SkillInterface class name to a String, as I only need to persist the classname of the current skill strategy of creature, with a String like skill.getClass().getSimpleName(). I tried to implement it with #Converter annotation, using AttributeConverter class to convert SkillInterface to String and save, but always had mapping exceptions. I want to be able to save it as String and retrieve as SkillInterface object.
But how can I implement it with Hibernate? Or do I have a design mistake?
Ok looks like I have found a basic solution that can be used to persist Strategy Pattern interfaces implementations. I used a #Converter annotation and a AttributeConverter class to convert strategy class names to column while saving to database and cast the retrieved String back to strategy class as following:
#Entity
public class Creature {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Convert(converter = SkillConverter.class)
private SkillInterface skill;
}
public class SkillConverter implements AttributeConverter<SkillInterface,String> {
#Override
public String convertToDatabaseColumn(SkillInterface skill) {
return skill.getClass().getSimpleName().toLowerCase();
}
#Override
public SkillInterface convertToEntityAttribute(String dbData) {
//works as a factory
if (dbData.equals("noskill")) {
return new NoSkill();
} else if (dbData.equals("axe")) {
return new Axe();
}
return null;
}
}
public interface SkillInterface {
public String getSkill();
void attack();
}
public class NoSkill implements SkillInterface{
public String getSkill() {
return getClass().getSimpleName();
}
#Override
public void attack() {
//strategy statements
}
}
You can use a proxy field to this for you like below:
abstract class Creature {
#Column
private String name;
// strategy pattern composition
private SkillInterface skill;
#Column
private String skillName;
public String getSkillName() {
return skill.getClass().getSimpleName();
}
public void setSkillName(String skillName) {
//ignore
}
}

Map this/self of source to specific field in target in Orika

Let's have source classes
#Data
public class Source {
private String name;
}
#Data
public class SourceParent {
private String parentName;
}
and target classes
#Data
public class Target {
private String name;
private TargetParent parent;
}
#Data
public class TargetParent {
private String parentName;
}
As you can see in Source I don't have the parent reference.
What I do is
Source s = findSource();
SourceParent sp = findParentForSource(s);
Target t = mapperFactory.map(s, Target.class);
mapperFactory.map(sp, t); //<--- Here is the problem
The problem is that I cannot map a SourceParent object to a specific field in Target.
I know how to do this using custom mappers or another "manual" ways. Is there a way to do it "Orika way"?
Something like:
mapperFactory.classMap(SourceParent.class, Target.class)
.fieldAtoB("?myslef?","parent")
.register();
Try with
mapperFactory.classMap(SourceParent.class, Target.class)
.fieldAtoB("","parent")
.register();

Categories