How can I disable a field in source mapping in MapStruct? - java

For example, I have mapping class with a field, that not presented in mapped class.
A class, which I want to map:
#Entity
#Table(name = "t_connection")
#Getter #Setter
#EqualsAndHashCode
public class ConnectionEntity {
#NotNull
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
protected UUID id;
...
}
A class, in which I want to map:
#ApiModel
#Getter
#Setter
#NoArgsConstructor
public class ConnectionDto {
#ApiModelProperty
private LocalDateTime createAt;
...
// Other fields without id field
}
My mapper looks like this:
#Mapper(componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.ERROR,
unmappedSourcePolicy = ReportingPolicy.ERROR)
public interface CallMapper {
#IterableMapping(qualifiedByName = "map")
List<ConnectionDto> map(List<ConnectionEntity> connectionEntities);
ConnectionDto map(ConnectionEntity connectionEntity);
}
I want to know, when a specific field is not been mapped, so disabling unmappedSourcePolicy is not a option. Any advice?

If I understand you.. You want to control the source properties you don't want to map?
In that case try:
#BeanMapping#ignoreUnmappedSourceProperties
So:
#Mapper(componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.ERROR,
unmappedSourcePolicy = ReportingPolicy.ERROR)
public interface CallMapper {
#IterableMapping(qualifiedByName = "map")
List<ConnectionDto> map(List<ConnectionEntity> connectionEntities);
#BeanMapping( ignoreUnmappedSourceProperties={"id"} )
ConnectionDto map(ConnectionEntity connectionEntity);
}
You don't need to specify a list mapping, unless you need this from the outside.. MapStruct will generate one for your.. If you do need the list from the outside, you possibly don't need the qualifier.. The Generic + List are enough

Related

Scan all fields in dtos and find missing and extra fields by their Entities

I want to create a unit test that will use reflection to find all missing fields in dto that implement BaseDto by their persistence entities. This is what I did.
#Slf4j
public class EntityAuditDtoTest {
#Test
public void find_MissingAndExtraFieldsThatUsedInAuditDtosByEntity_ReturnMissingAndExtraFields() throws ClassNotFoundException {
// Arrange
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(AuditEntityType.class));
// Find all classes annotated with #AuditEntityType in the package com.example.dto
Set<BeanDefinition> auditDtoBeans = scanner.findCandidateComponents("com.example.dto");
// Act
for (BeanDefinition auditDtoBean : auditDtoBeans) {
Class<?> auditDtoClass = Class.forName(auditDtoBean.getBeanClassName());
// Make sure the DTO class implements BaseAuditDto
if (!BaseAuditDto.class.isAssignableFrom(auditDtoClass)) {
continue;
}
Class<?> entityClass = getEntityClassForDto(auditDtoClass);
Field[] dtoFields = auditDtoClass.getDeclaredFields();
Field[] entityFields = entityClass.getDeclaredFields();
List<String> missingFields = Arrays.stream(entityFields).map(Field::getName)
.filter(field -> Arrays.stream(dtoFields).noneMatch(f -> f.getName().equals(field))).toList();
if (!missingFields.isEmpty()) {
log.error("Missing fields in DTO class: {} \nfor entity class: {} : {}", auditDtoClass.getName(),
entityClass.getName(), missingFields);
}
List<String> extraFields = Arrays.stream(dtoFields).map(Field::getName)
.filter(field -> Arrays.stream(entityFields).noneMatch(f -> f.getName().equals(field))).toList();
if (!extraFields.isEmpty()) {
log.error("Extra fields in DTO class: {} \nfor entity class: {} : {}", auditDtoClass.getName(),
entityClass.getName(), extraFields);
}
}
}
}
But the problem is that the dto may have a field that is in the entity class, but the test will think that this is a missing field.
For example:
Dto class: ContractAudit has customerId field (customerId). And ContractEntity has public CustomerEntity customer.
This is the same fields. But of course for test they are different. I don't understand how to ignore them. I also don't want to hardcode filter that skip all endings with 'id' prefix.
#Data
#AuditEntityType("Contract")
public class ContractAudit implements BaseAuditDto {
private Long id;
private String ref;
private String status;
private Long customerId;
}
#Entity
#Table(name = "contract")
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class ContractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
#ToString.Include
private Long id;
#Column(name = "ref", updatable = true)
#ToString.Include
private String ref;
#Column(name = "status")
#ToString.Include
#Enumerated(value = EnumType.STRING)
private ContractStatusEnum status;
#ManyToOne
#JoinColumn(name = "customer_id")
public CustomerEntity customer;
#Column(name = "deleted")
#ToString.Include
private boolean deleted;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "contract_id")
private List<ContractDocumentEntity> documents;
}
Output:
Missing fields in DTO class: ContractAudit for entity class: ContractEntity : [customer, deleted, documents]
Extra fields in DTO class: ContractAudit for entity class: ContractEntity : [customerId]
I want to have missing fields: [deleted, documents]
If you have any other ideas on how to do this, I'd love to hear it. I am not asking for implementation. Suggestions only)
Lol. I found solution for my case.
My previous approach was incorrect. Because it's impossible to find 'missing' and 'extra' fields by name correctly for every case. I decided to use:
assertThat(entityClass.getDeclaredFields()).hasSameSizeAs(auditDtoClass.getDeclaredFields());
So this code is checking if the entityClass and the DtoClass have the same number of fields (properties) declared. If not it fail test and print all fields from each classes. If anyone has better ideas I'll be happy to hear.

Why entity objects are not equal if I get one of that object with another fetched object that has collection elements although they should be?

I have 3 entity clasess that have relationships on each others
TestClassParent :
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(of = "email")
#Inheritance(strategy = InheritanceType.JOINED)
public class TestClassParent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
}
TestClassChild :
#Entity
#Data
#EqualsAndHashCode(callSuper = true)
public class TestClassChild extends TestClassParent{
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "test_collection_id")
private TestChildCollection testChildCollection;
}
TestChildCollection :
#Entity
#Data
#EqualsAndHashCode(of ="id")
#AllArgsConstructor
#NoArgsConstructor
public class TestChildCollection {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "testChildCollection",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<TestClassChild> testClassChildSet;
}
Currently my database look like this:
Relationships :
Equalitiy of objects is done by comparing their email
I have code for testing this case:
#SpringBootApplication
#AllArgsConstructor
public class DemoApplication {
private final TestClassChildRepository testClassRepository;
private final TestChildCollectionRepository testChildCollectionRepository;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public ApplicationRunner applicationRunner() {
return args -> {
TestClassChild testClassChild = testClassRepository.findById(1L).get();
TestClassChild testClass1 = new TestClassChild();
testClass1.setId(testClassChild.getId());
testClass1.setEmail(new String("newTestEmail2"));
System.out.println(testClass1.equals(testClassChild));
};
}
}
And I get false for comparing these objects
Result is :
I looked for debug and saw that, first instance has hashcode in email, and another one doesn't
First one:
Second one:
testClassRepository.findById(1L).get(); returns an object with testChildCollection. When you generate a new TestClassChild you set id and email, but not the collection, but in your example Lombok also uses the testChildCollection to calculate equals/hashCode.
When using inheritance I usually try to avoid using #Data, because it's easy to loose track of how the equals/hashCode are actually generated.
#Entity
#Getter
#Setter
#ToString(callSuper = true) // Only if you really need a toString implementation that also prints your collection.
public class TestClassChild extends TestClassParent
Will do the same just without the hashCode/equals generation.
The problem is that your TestClassChild equals() method also include a check for its testChildCollection property. The variable testClassChild has such property as not null, but testClass1 has it as null. Hence the call testClass1.equals(testClassChild) will always be false.
If you only want to rely on the superclass equals() implementation, then you will have to exclude testClassChild as follows:
#Entity
#Data
#EqualsAndHashCode(callSuper = true)
public class TestClassChild extends TestClassParent{
#EqualsAndHashCode.Exclude
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "test_collection_id")
private TestChildCollection testChildCollection;
}

How to get a child entity through parent field

I have a spring boot application with two entities. I am trying to get one entity through its foreing key but am getting the error, that it is not present eventhough it is present their. Maybe someone can give me a pointer:
#Entity
#Table(name = "meeting_settings")
#Setter
#Getter
public class MeetingsSetting implements Serializable {
#JsonManagedReference
#OneToMany(mappedBy = "meetingName", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<MeetingTime> meetingTime = new HashSet<>();
}
MeetingTimeEntity
#Entity
#Table(name = "meeting_times")
#Getter
#Setter
public class MeetingTime implements Serializable {
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "meeting_name" ,insertable = false, updatable = false, referencedColumnName = "meeting_name")
private MeetingsSetting meetingName;
}
This is my repository MeetingTime:
#Repository
public interface MeetingTimeRepository extends JpaRepository<MeetingTime, Long> {
public List<MeetingTime> findMeetingTimeByMeetingName(MeetingTime meeting_name);
}
And finally my service:
#Service
public class MeetingTimeService {
#Autowired
MeetingTimeRepository meetingTimeRepository;
public List<MeetingTime> findMeetingTimeByName(MeetingTime meeting_name){
return meetingTimeRepository.findMeetingTimeByMeetingName(meeting_name);
}
}
And the controller which is calling it:
#CrossOrigin(origins = "http://localhost:8080")
#RestController
#RequestMapping("api/meetingTime")
public class MeetingTimeController {
#Autowired
MeetingTimeService meetingTimeService;
#GetMapping(value = "/{meeting_name}" )
public List<MeetingTime> getTimesByName(#RequestParam MeetingTime meeting_name){
return meetingTimeService.findMeetingTimeByName(meeting_name);
}
}
THe error I am getting is the following:
Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'meeting_name' for method parameter type MeetingTime is not present]
This happens when I give MeetingSetting as a Pathvariable, I tried giving it string because the foreign key meeting_name is a varchar but then I am getting the error that the expected type was MeetingSetting. If I use forexample date to find my entities they are found easily
I think , #RequestParam is invalid... it must change to #PathParam
First, why do you use MeetingTime for parameter to findMeetingTimeByMeetingName? From what I see, you use MeetingsSetting as the join column in the entity, so you should use MeetingsSetting instead of MeetingTime.
Also, you can try to do the query using the foreign key meeting_name value
Try to use this one on the MeetingTimeRepository
public List<MeetingTime> findAllByMeetingNameId(String meetingNameId);
I'm assuming you have id field on the MeetingsSetting with String data type.
Lastly, I don't think you can use Object as path variable as it can have multiple values.
I kinda fixed it by changing repo method to this:
List<MeetingTime> findMeetingTimeByMeetingName_MeetingName(String meetingName);
And using #RequestParam like this: #RequestParam("meetingName")
in the controller

Mapping nested list

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

Problems in Implementing a Custom Behaviour in mapstruct Mapper

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

Categories

Resources