实体转DTO触发StackOverflowError如何调整Mapper解决?
问题解决:拉取关联用户的任务时StackOverflowError
根因分析
报错栈显示转换逻辑出现无限递归:TaskMapper#convertToTaskDTO 调用 UserMapper#convertToUserDTO 转换关联用户,而UserMapper#convertToUserDTO内部又会遍历用户关联的任务列表,逐个调用TaskMapper#convertToTaskDTO转换任务,循环调用最终触发栈溢出。
注意:User实体类中taskList字段加的@JsonIgnore仅作用于JSON序列化阶段,不会拦截你手写的Mapper转换逻辑,所以该注解对这个问题无效。
解决方案
方案1:直接移除UserDTO中的任务列表字段(最推荐)
绝大多数嵌套场景下,TaskDTO里的用户信息不需要额外携带该用户的所有任务,直接删掉UserDTO中的List<TaskDTO> taskList字段,同时删除UserMapper转换逻辑中填充taskList的代码即可:
// 修改后的UserMapper#convertToUserDTO public UserDTO convertToUserDTO(User user) { return UserDTO.builder() .id(user.getId()) .firstName(user.getFirstName()) .lastName(user.getLastName()) .email(user.getEmail()) // 移除taskList相关转换逻辑 .build(); }
方案2:分层DTO拆分适配不同场景
如果业务确实存在「单独查询用户需要返回任务列表」的需求,可以拆分两个不同粒度的UserDTO:
- 简化版用户DTO,用于TaskDTO嵌套场景,不带任务列表:
@Data @Builder public class UserSimpleDTO { private Long id; private String firstName; private String lastName; private String email; }
- 完整版用户DTO,用于用户详情查询场景,带任务列表:
@Data @Builder public class UserFullDTO { private Long id; private String firstName; private String lastName; private String email; private List<TaskDTO> taskList; }
再修改TaskDTO中的用户字段类型为UserSimpleDTO,TaskMapper转换时调用UserMapper的convertToUserSimpleDTO方法即可,不会触发递归。
方案3:增加循环引用截断逻辑
如果不方便修改DTO结构,可以在Mapper中增加已转换对象的缓存判断,遇到已经转换过的对象直接返回,避免递归:
// 在Mapper中增加ThreadLocal缓存已经转换的对象ID private final ThreadLocal<Set<Long>> processedTaskIds = ThreadLocal.withInitial(HashSet::new); public TaskDTO convertToTaskDTO(Task task) { if (processedTaskIds.get().contains(task.getId())) { return null; // 或者返回仅带ID的简化DTO } processedTaskIds.get().add(task.getId()); // 原有转换逻辑 TaskDTO dto = TaskDTO.builder() .id(task.getId()) .creationDate(task.getCreationDate()) .description(task.getDescription()) .name(task.getName()) .status(task.getStatus()) .priority(task.getPriority()) .userDTO(userMapper.convertToUserDTO(task.getUser())) .build(); processedTaskIds.get().remove(task.getId()); return dto; }
该方案侵入性高,仅作为临时兼容方案使用。
内容的提问来源于stack exchange,提问作者Олександр Житарюк
相关产品推荐
相关产品推荐

