I have two classes with two types of relations - one is @ManyToMany
and the other one is @OneToMany
(one relates to Project
- Members of project
relationship and the other is related with Project
- Owner of project
where owner is the same type as members). I had problem with infinite recursion in controller but soon, I managed to resolve this issue with usage of @JsonIdentityInfo
. But then I realized that one of classes shows too much information about client's data - passwords, usernames, etc., so i created DTO objects and implemented logic for mapping original objects to DTOs. Logic was simple - return DTOs so I control exposed data.
But there is a problem - if I create mapper for first class to map its' objects into DTOs, then second class must be mapped to DTOs too. But - regarding to provided example - second class must also expose information about its' owner, so we need to simultaneously map first class and second. It creates circular dependency problem.
Mapper classes:
public class ProjectMapper {
public static ProjectDTO mapProjectToDTO(Project projectToMap) {
ProjectDTO newDTO = new ProjectDTO();
List<VolunteerDTO> listOfDTOs = projectToMap.getProjectVolunteers()
.stream()
.map(VolunteerMapper::mapVolunteerToDTO)
.collect(Collectors.toList());
newDTO.setProjectName(projectToMap.getProjectName());
newDTO.setProjectDescription(projectToMap.getProjectDesription());
newDTO.setProjectLocation(projectToMap.getProjectLocation());
newDTO.setProjectStatus(projectToMap.isProjectStatus());
newDTO.setProjectDate(projectToMap.getProjectDate());
newDTO.setProjectOpinions(projectToMap.getProjectOpinions());
newDTO.setProjectOwner(VolunteerMapper.mapVolunteerToDTO(projectToMap.getOwnerVolunteer()));
newDTO.setProjectVolunteers(listOfDTOs);
newDTO.setTasks(projectToMap.getTasks());
newDTO.setRequiredSkills(projectToMap.getRequiredSkills());
return newDTO;
}
public static void mapPropertiesToProject(Project sourceProject, Project targetProject) {
targetProject.setProjectName(sourceProject.getProjectName());
targetProject.setProjectDate(sourceProject.getProjectDate());
targetProject.setProjectVolunteers(sourceProject.getProjectVolunteers());
targetProject.setProjectDesription(sourceProject.getProjectDesription());
targetProject.setProjectLocation(sourceProject.getProjectLocation());
targetProject.setProjectOpinions(sourceProject.getProjectOpinions());
targetProject.setProjectStatus(sourceProject.isProjectStatus());
}
}
public class VolunteerMapper {
public static VolunteerDTO mapVolunteerToDTO(Volunteer volunteerToMap) {
VolunteerDTO newDTO = new VolunteerDTO();
List<ProjectDTO> listOfDTOs = volunteerToMap.getParticipatingProjects()
.stream()
.map(ProjectMapper::mapProjectToDTO)
.collect(Collectors.toList());
newDTO.setName(volunteerToMap.getName());
newDTO.setSurname(volunteerToMap.getSurname());
newDTO.setDateOfBirth(volunteerToMap.getDateOfBirth());
newDTO.setContact(volunteerToMap.getContact());
newDTO.setSkills(volunteerToMap.getSkills());
newDTO.setProjects(listOfDTOs);
newDTO.setReputation(volunteerToMap.getReputation());
newDTO.setInterests(volunteerToMap.getInterests());
return newDTO;
}
public static void mapPropertiesToVolunteer(Volunteer sourceVolunteer, Volunteer targetVolunteer) {
targetVolunteer.setName(sourceVolunteer.getName());
targetVolunteer.setSurname(sourceVolunteer.getSurname());
targetVolunteer.setDateOfBirth(sourceVolunteer.getDateOfBirth());
targetVolunteer.setContact(sourceVolunteer.getContact());
targetVolunteer.setSkills(sourceVolunteer.getSkills());
targetVolunteer.setParticipatingProjects(sourceVolunteer.getParticipatingProjects());
targetVolunteer.setReputation(sourceVolunteer.getReputation());
targetVolunteer.setInterests(sourceVolunteer.getInterests());
}
}
Model classes:
@Data
public class ProjectDTO {
private String projectName;
private String projectDescription;
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate projectDate;
private List<VolunteerDTO> projectVolunteers;
private VolunteerDTO projectOwner;
private String projectLocation;
private List<String> requiredSkills;
private boolean projectStatus;
private List<Opinion> projectOpinions;
private List<String> tasks;
}
@Data
public class VolunteerDTO {
private String name;
private String surname;
private LocalDate dateOfBirth;
private String contact;
private List<String> skills;
private List<ProjectDTO> projects;
private Integer reputation;
private List<String> interests;
}
Problem
public static ProjectDTO mapProjectToDTO(Project projectToMap) {
List<VolunteerDTO> listOfDTOs = projectToMap.getProjectVolunteers()
.stream()
====>> .map(VolunteerMapper::mapVolunteerToDTO) <<===
.collect(Collectors.toList());
}
public static VolunteerDTO mapVolunteerToDTO(Volunteer volunteerToMap) {
List<ProjectDTO> listOfDTOs = volunteerToMap.getParticipatingProjects()
.stream()
====>> .map(ProjectMapper::mapProjectToDTO) <<====
.collect(Collectors.toList());
}
mapProjectToDto calls mapVolunteerToDTO which calls mapProjectToDto again.
you can break that circle in different ways depending on your business case
1- make VolunteerDto without a list of ProjectDto and whenever you need such list you can fetch it using VolunteerDto Id
2- make ProjectDto without list of VolunteerDto (same like 1)
3- make VolunteerDto contains list of Project Ids intead of list of ProjectDTOs
4- make ProjectDTO contain list of VolunteerIds instead of VolunteerDtos
I would prefer 3rd, but it's all depending on your business case