You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实体转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:

  1. 简化版用户DTO,用于TaskDTO嵌套场景,不带任务列表:
@Data
@Builder
public class UserSimpleDTO {
    private Long id;
    private String firstName;
    private String lastName;
    private String email;
}
  1. 完整版用户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,提问作者Олександр Житарюк

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 19:27:04