I have a object with list of nested object
#Getter
#Setter
#NoArgsConstructor
public class Notification {
private Long id
private Long statusId;
private List <External> external;
}
#Getter
#Setter
#NoArgsConstructor
public class External{
private Long externalId;
private LocalDate date;
}
Dto
#Getter
#Setter
#NoArgsConstructor
public class NotificationPayload {
private Long id;
private Long statusId;
private List <ExternalReferencePayload> external;
}
#Getter
#Setter
#NoArgsConstructor
public class ExternalReferencePayload {
private Long externalReferenceId;
}
Mapper
#Mapper(componentModel = "spring")
public interface NotificationMapper{
public Notification dtoToNotification(NotificationPayload payload);
}
I search the way to map the nested list
In order to perform custom mapping for certain elements it is only needed to define a mapping method and MapStruct will take care of the rest. In your example:
#Mapper(componentModel = "spring")
public interface NotificationMapper{
public Notification dtoToNotification(NotificationPayload payload);
#Mapping(target = "externalId", source = "externalReferenceId")
public External dtoExternal(ExternalReferencePayload payload);
}
With this the nested list will use the dtoExternal mapping method to perform the mapping. With #Mapping you control how the mapping between externalId and externalReferenceId should be done
Related
I'm trying to create a mapper using lombok #Mapper and I get the following error:
Property "delivery" has no write accessor in ClassA for target name "classBB_variable.delivery"
The code:
#Mapper
#Mapping(target = "classBB_variable.customerId", source = "classA_variable.customerId")
#Mapping(target = "classBB_variable.delivery", source = "classA_variable.classAA_variable.delivery")
ClassB toCreateRequest(ClassA classA_variable);
#Value
#Builder(toBuilder = true)
public class ClassA {
private UUID customerId;
private ClassAA classAA_variable;
}
#Data
#Builder
public class ClassAA {
private String delivery;
}
#Data
#Builder
public class ClassB {
private ClassBB classBB_variable;
}
#Data
#Builder
public class ClassBB {
private UUID customerId;
private String delivery;
}
As the map for customerId works fine, I'm assuming the issue is the additional level introduced with classA_variable.classAA_variable.delivery.
Has anyone already dealt with this situation? How can I solve it?
I am trying to map DTO to the corresponding #Entity in the service layer.
Condition may be of a set of types: Amount, Title, Date. Each condition, except Amount, has a unique predefined set of clauses.
TitleCondition: includes, startsWith
DateCondition: from, until
The idea is to use common Condition entity with #Inheritance(strategy= InheritanceType.TABLE_PER_CLASS).
The 2 problems I see with this code is:
It is unclear how to properly set data
data type is Object
Is there a way to use convenient Lombok's #Builder with given mapping? What would be the simpler and better way to map dto to entity?
Service:
#Service
public class FilterService {
private Condition convertConditionDtoToEntity(ConditionDto conditionDto) {
Type type = typeRepository.findFirstByName(conditionDto.getType())
.orElseThrow(UnsupportedOperationException::new);
Clause clause;
if (conditionDto.getClause() != null) {
clause = clauseRepository.findFirstByName().orElseThrow(UnsupportedOperationException::new);
}
if (conditionDto.getType().equals("amount")) {
return AmountCondition.builder().type(type).data(???).build();
} else if (conditionDto.getType().equals("title")) {
return TitleCondition.builder().type(type).clause(clause).data(???).build();
} else if (conditionDto.getType().equals("date")) {
return DateCondition.builder().type(type).clause(clause).data(???).build();
} else {
throw new UnsupportedOperationException();
}
}
}
Condition
#Getter
#SuperBuilder
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Inheritance(strategy= InheritanceType.TABLE_PER_CLASS)
public abstract class Condition {
#Id
#GeneratedValue
private long id;
#ManyToOne
private Filter filter;
#Getter
#ManyToOne
public Type type;
public abstract Object getData();
}
DateCondition
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class DateCondition extends Condition {
#Column
int clauseId;
#Column
#Temporal(TemporalType.DATE)
Date date;
#Getter
#ManyToOne
private Clause clause;
#Override
public Object getData() {
return date;
}
}
TitleCondition
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class TitleCondition extends Condition {
#Column
int clauseId;
#Column
String title;
#Getter
#ManyToOne
private Clause clause;
#Override
public Object getData() {
return title;
}
}
AmountCondition
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class AmountCondition extends Condition {
#Column
int amount;
#Override
public Object getData() {
return amount;
}
}
Clause
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Clause {
#Id
#GeneratedValue
private long id;
#Column
String name;
#OneToMany(mappedBy = "clause")
private Set<Type> types = new HashSet();
}
Type
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Type {
#Id
#GeneratedValue
private long id;
#Column
String name;
#ManyToOne
private Clause clause;
#OneToMany(mappedBy = "type")
private Set<Condition> conditions;
}
I have two forms that I use a lot and save a lot of time.
first: transform your model into json and convert the json to object the Mapper class (this link will help https://www.baeldung.com/jackson-object-mapper-tutorial)
second: spring has some cool functions about it. an example would be the BeanUtils.copyProperties function (source, target);
data problem: the data field does not exist for this reason you do not need to set it.
Another problem that I was able to notice is that their properties are all defaulted so set them as private.
If I have a Class ProfessorDto and a Class StudentDto how can I avoid circular issues if the ProfessorDto have a list of StudentDto and the StudentDto have a property of type ProfessorDto ?
I didn't put the code of the domain class but let's say it is the same as for the Dto.
I'm new to Mapstruct, converting a domain bean to a Dto with simple properties like Long, String is working but in my exemple, the relation OneToMany is not working !
#JsonApiResource(type = "professor")
#NoArgsConstructor
#Data
public class ProfessorDto {
#JsonApiId
private Long id;
private String professorName;
#JsonApiRelation(mappedBy = "professor")
private List<StudentDto> student;
public ProfessorDto(Long id) {
this.id = id;
}
}
And a class Student
#JsonApiResource(type = "student")
#NoArgsConstructor
#Data
public class StudentDto {
#JsonApiId
private Long id;
private String studentName;
#JsonApiRelation
private ProfessorDto professor;
}
My Mapper for Professor is
#Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ProfessorMapper {
ProfessorDto domainToDto(Professor domain);
Professor dtoToDomain(ProfessorDto dto);
StudentDto studentToDto(Student student);
Student studentDtoToDomain(StudentDto studentDto);
List<StudentDto> studentToDto(List<Student> student);
List<Student> studentDtoToDomain(List<StudentDto> studentDto);
}
check out this example using #Context annotation
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-mapping-with-cycles/src/main/java/org/mapstruct/example/mapper
more about the #Context annotation can be found at the Mapstruct documentation below
https://mapstruct.org/documentation/stable/api/org/mapstruct/Context.html
At first, you should decide if it is really needed to keep List<StudentDto> aggregated in ProfessorDto. You can just exclude it, if it's possible.
Otherwise, you can make either StudentDto or ProfessorDto 'flat'. For example, you can add field Long professorId instead of ProfessorDto professor to the StudentDto, or List<Long> studentIds to the ProfessorDto.
An sample approach using Mapstruct:
Change a DTO class like this:
#JsonApiResource(type = "student")
#NoArgsConstructor
#Data
public class StudentDto {
#JsonApiId
private Long id;
private String studentName;
private Long professorId; // <-- CHANGED
}
Add a Helper class, and provide custom mapping logic:
#Component
public class MapperHelper {
#AfterMapping
public void mapProfessorId(Student student, #MappingTarget StudentDto studentDto) {
studentDto.setProfessorId(student.getProfessor().getId());
}
}
I have a SpringBoot 2 app that uses using Couchbase as a database, Spring-Boot and Spring-Data and Lombok fot the getters and equals method
I have created this Repository
#ViewIndexed(designDoc = "bendicionesDoc")
public interface BenRepository extends CouchbaseRepository<BendicionesDoc, String> {
#Query("#{#n1ql.selectEntity} where #{#n1ql.filter} AND ANY uuid IN data.identifier.id SATISFIES uuid = $1 END")
List<BendicionesDoc<Item>> findById(String id);
}
and here all the objects created with Lombok library
public class BendicionesDoc<T>implements Serializable {
#Field
private T data;
}
and
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(NON_NULL)
public class Item {
private List<Identifier> identifier;
}
and
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(NON_NULL)
#EqualsAndHashCode
public class Identifier {
private String id;
private MasterServant idContext;
private MasterServant idScope;
}
and
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(NON_NULL)
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class MasterServant {
private String context;
#JsonValue
#EqualsAndHashCode.Include
private String value;
private Name valueDescription;
#JsonCreator
public MasterServant(String value) {
this.value = value;
}
}
But when I run the repository query I got always 0 results, even there are docs. in the DB:
You need to define your reference type in CouchbaseRepository<T, K> then simply add the reference type Item as CouchbaseRepository<BendicionesDoc<Item>, String> and just use Repository query keywords for findById(String id).
public interface BenRepository extends CouchbaseRepository<BendicionesDoc<Item>, String> {
List<BendicionesDoc<Item>> findById(String id);
}
I have the following DTO classes:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Conclusion {
private Integer id;
// ......
private List<CType> cTypes;
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class CType {
private Integer id;
// ......
private VType vType;
}
And also their corresponding entity classes:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "conclusion")
public class Conclusion {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, updatable = false)
private Integer id;
// ......
#OneToMany
#JoinColumn(name = "id")
private List<CTypeEntity> cTypeEntities;
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "c_types")
public class CTypeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, updatable = false)
private Integer id;
// ......
#Column(name = "v_type_id")
private Integer vTypeId;
}
Also, all corresponding Dao and JPA Repository interfaces are present.
Now, I am writing my mapstruct Mapper interface which should be used for mapping entities to DTOs and vice versa. Here is the mapper:
#Mapper
public interface ConclusionMapper {
#Mappings({
#Mapping(target = "cTypes", source = "cTypeEntities")
})
Conclusion toConclusion(ConclusionEntity entity);
List<Conclusion> toConclusionList(List<ConclusionEntity> entities);
#Mappings({
#Mapping(target = "cTypeEntities", source = "cTypes")
})
ConclusionEntity fromConclusion(Conclusion conclusion);
List<ConclusionEntity> fromConclusionList(List<Conclusion> conclusions);
#Mappings({
#Mapping(target = "cType", source = "vTypeId", qualifiedByName = "formVType")
})
ConclusionTaxType toCType(CTypeEntity cTypeEntity);
List<CType> toCTypes(List<CTypeEntity> cTypeEntities);
CTypeEntity fromCType(CType cType);
List<CTypeEntity> fromCTypeList(List<CType> cTypes);
#Named("formVType")
default VType formVType(CTypeEntity entity) {
// TODO: instantiate a DAO which will return an instance of VType
VTypeDao dao; // instantiate somehow
return vTypeDao.findById(entity.getVId());
}
}
VTypeDao looks like this:
public interface VTypeDao {
VType findById(int id);
boolean exists(int id);
List<VType> findAll();
}
#Component
public class VTypeDaoImpl implements VTypeDao {
private final VTypeRepo vTypeRepo;
public VTypeDaoImpl(VTypeRepo vTypeRepo) {
this.vTypeRepo = vTypeRepo;
}
// ............................. method implementations
}
My question is: How to instantiate an object of VTypeDao (or at least VTypeRepo so I could pass if to VTypeDaoImpl as a parameter)?
There is no factory class for getting the appropriate implementation of VTypeDao.
EDIT: VTypeDao and its implementation are third party components to my project.
From your comment I got you want to do lookups on your VTypeDao during the mapping process. You can wrap it in another class and hand it as #Context annotated as mapping argument. MapStruct will not consider such class as source or target. However it will call lifecycle methods in this class.. have a look at this example: https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-jpa-child-parent/src.
It's jpa based, but you can map it easily to your problem..
I eventually ended up with the following implementation:
#Mapper
public interface ConclusionMapper {
#Mappings({
#Mapping(target = "cTypes", source = "cTypeEntities")
})
Conclusion toConclusion(ConclusionEntity entity);
List<Conclusion> toConclusionList(List<ConclusionEntity> entities);
#InheritInverseConfiguration
ConclusionEntity fromConclusion(Conclusion conclusion);
List<ConclusionEntity> fromConclusionList(List<Conclusion> conclusions);
#Mappings({
#Mapping(target = "vType", ignore = true)
})
ConclusionTaxType toCType(CTypeEntity cTypeEntity);
List<CType> toCTypes(List<CTypeEntity> cTypeEntities);
#Mappings({
#Mapping(target = "vTypeId", source = "vType.id")
})
CTypeEntity fromCType(CType cType);
List<CTypeEntity> fromCTypeList(List<CType> cTypes);
}
So I just ignored vType member in entity to DTO mapping and put it manually in my DAO since that was the most simple way of doing.
Integrate map-struct with spring :
http://mapstruct.org/documentation/stable/reference/html/#using-dependency-injection
Replace #Mapper with below
#Mapper(componentModel = "spring")
and add below in VTypeDaoImpl
#Autowired
private ConclusionMapper conclusionMapper;
Execute below maven command (Assuming you are using maven)
mvn clean compile
sources will be generated in targeted/generated-sources/annotations