Mapper类循环依赖求助:Project与Volunteer双向映射困境
问题描述
我有两个实体类,存在@ManyToMany和@OneToMany两种关联关系:Project与项目成员是多对多关联,Project与项目所有者是一对多关联(所有者和成员都属于Volunteer类型)。之前用@JsonIdentityInfo解决了控制器的无限递归问题,但为了避免暴露用户敏感数据(比如密码、用户名),我创建了DTO对象并手动实现实体到DTO的映射逻辑,现在遇到了Mapper类循环依赖的问题:ProjectMapper映射时需要调用VolunteerMapper,VolunteerMapper映射时又要调用ProjectMapper,形成循环调用导致栈溢出。
Mapper类代码
ProjectMapper.java
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()); } }
VolunteerMapper.java
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()); } }
DTO类代码
ProjectDTO.java
@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; }
VolunteerDTO.java
@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; }
解决方案
方法1:截断循环引用,只保留单向关联
根据业务场景决定哪一端不需要包含完整的关联对象,比如:
- 查询Project时,返回的VolunteerDTO不需要包含关联的Project列表(或者只保留项目ID)
- 查询Volunteer时,返回的ProjectDTO不需要包含关联的Volunteer列表
示例修改:给VolunteerMapper新增重载方法,映射不含项目的VolunteerDTO
// VolunteerMapper中新增方法 public static VolunteerDTO mapVolunteerToDTOWithoutProjects(Volunteer volunteerToMap) { VolunteerDTO newDTO = new VolunteerDTO(); newDTO.setName(volunteerToMap.getName()); newDTO.setSurname(volunteerToMap.getSurname()); newDTO.setDateOfBirth(volunteerToMap.getDateOfBirth()); newDTO.setContact(volunteerToMap.getContact()); newDTO.setSkills(volunteerToMap.getSkills()); newDTO.setReputation(volunteerToMap.getReputation()); newDTO.setInterests(volunteerToMap.getInterests()); // 不设置projects字段,切断循环 return newDTO; }
然后修改ProjectMapper中的调用:
// ProjectMapper中替换原有的VolunteerMapper调用 List<VolunteerDTO> listOfDTOs = projectToMap.getProjectVolunteers() .stream() .map(VolunteerMapper::mapVolunteerToDTOWithoutProjects) .collect(Collectors.toList()); newDTO.setProjectOwner(VolunteerMapper.mapVolunteerToDTOWithoutProjects(projectToMap.getOwnerVolunteer()));
方法2:使用映射框架自动处理循环引用
像MapStruct这类成熟的映射框架支持自动处理循环引用,不需要手动写繁琐的映射逻辑。通过@Named和@Mapping注解指定关联对象的映射规则,切断循环:
示例配置:
// ProjectMapper接口 @Mapper public interface ProjectMapper { ProjectMapper INSTANCE = Mappers.getMapper(ProjectMapper.class); @Mapping(target = "projectVolunteers", qualifiedByName = "volunteerWithoutProjects") @Mapping(target = "projectOwner", qualifiedByName = "volunteerWithoutProjects") ProjectDTO toDTO(Project project); @Named("volunteerWithoutProjects") @Mapping(target = "projects", ignore = true) VolunteerDTO volunteerToDTOWithoutProjects(Volunteer volunteer); } // VolunteerMapper接口 @Mapper public interface VolunteerMapper { VolunteerMapper INSTANCE = Mappers.getMapper(VolunteerMapper.class); @Mapping(target = "projects", qualifiedByName = "projectWithoutVolunteers") VolunteerDTO toDTO(Volunteer volunteer); @Named("projectWithoutVolunteers") @Mapping(target = "projectVolunteers", ignore = true) @Mapping(target = "projectOwner", ignore = true) ProjectDTO projectToDTOWithoutVolunteers(Project project); }
方法3:手动控制映射深度
在映射方法中添加深度参数,当超过指定深度时停止映射关联对象,避免无限循环:
示例修改:
// ProjectMapper修改后的映射方法 public static ProjectDTO mapProjectToDTO(Project projectToMap, int depth) { if (depth <= 0) { // 深度为0时只返回基本字段,不映射关联对象 ProjectDTO newDTO = new ProjectDTO(); 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.setTasks(projectToMap.getTasks()); newDTO.setRequiredSkills(projectToMap.getRequiredSkills()); return newDTO; } ProjectDTO newDTO = new ProjectDTO(); List<VolunteerDTO> listOfDTOs = projectToMap.getProjectVolunteers() .stream() .map(v -> VolunteerMapper.mapVolunteerToDTO(v, depth - 1)) .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(), depth - 1)); newDTO.setProjectVolunteers(listOfDTOs); newDTO.setTasks(projectToMap.getTasks()); newDTO.setRequiredSkills(projectToMap.getRequiredSkills()); return newDTO; } // VolunteerMapper修改后的映射方法 public static VolunteerDTO mapVolunteerToDTO(Volunteer volunteerToMap, int depth) { if (depth <= 0) { // 深度为0时只返回基本字段 VolunteerDTO newDTO = new VolunteerDTO(); newDTO.setName(volunteerToMap.getName()); newDTO.setSurname(volunteerToMap.getSurname()); newDTO.setDateOfBirth(volunteerToMap.getDateOfBirth()); newDTO.setContact(volunteerToMap.getContact()); newDTO.setSkills(volunteerToMap.getSkills()); newDTO.setReputation(volunteerToMap.getReputation()); newDTO.setInterests(volunteerToMap.getInterests()); return newDTO; } VolunteerDTO newDTO = new VolunteerDTO(); List<ProjectDTO> listOfDTOs = volunteerToMap.getParticipatingProjects() .stream() .map(p -> ProjectMapper.mapProjectToDTO(p, depth - 1)) .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; }
调用时指定深度(比如ProjectMapper.mapProjectToDTO(project, 1)),限制关联对象的映射层数,避免无限循环。
内容的提问来源于stack exchange,提问作者Thorvas
相关产品推荐
相关产品推荐

