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

如何避免DTO映射过程中出现Stack Overflow(栈溢出)问题?

解决双向关联映射导致的栈溢出问题

问题背景

在处理JPA实体与DTO的双向关联映射时,当前的实现会触发无限递归调用:M_Mapper映射时会调用A_Mapper和C_Mapper,而A_Mapper/C_Mapper又会反过来调用M_Mapper,最终导致栈溢出。

实体类结构

public class A_Entity {
   private Set<M_Entity> ms;
   // getter/setter
}

public class C_Entity {
   private Set<M_Entity> ms;
   // getter/setter
}

public class M_Entity {
   private A_Entity a_entity;
   private C_Entity c_entity;
   // getter/setter
}

DTO类结构

public class A_DTO {
   private Set<M_DTO> ms;
   // getter/setter
}

public class C_DTO {
   private Set<M_DTO> ms;
   // getter/setter
}

public class M_DTO {
   private A_DTO a_dto;
   private C_DTO c_dto;
   // getter/setter
}

当前映射接口与实现问题

映射接口定义:

public interface EntityDTO<T1, T2> {
   T1 toEntity(T2 dto);
   T2 toDTO(T1 entity);
}

现有Mapper类的核心问题是循环调用+重复创建Mapper实例:比如M_Mapper.toEntity会创建A_Mapper并调用其toEntity,而A_Mapper.toEntity又会创建M_Mapper并调用其toEntity,无限递归直至栈溢出。


解决方案

方案1:引入映射缓存,避免重复映射

通过缓存记录已完成映射的对象,循环调用时直接返回缓存中的实例,终止递归。

修改映射接口

添加带缓存参数的重载方法:

public interface EntityDTO<T1, T2> {
   T1 toEntity(T2 dto);
   T2 toDTO(T1 entity);
   // 带缓存的重载方法,用于内部递归调用
   T1 toEntity(T2 dto, Map<Object, Object> cache);
   T2 toDTO(T1 entity, Map<Object, Object> cache);
}

修改A_Mapper实现

复用Mapper实例,添加缓存逻辑:

public class A_Mapper implements EntityDTO<A_Entity, A_DTO> {
   // 复用M_Mapper实例,避免重复创建
   private final M_Mapper mMapper = new M_Mapper();

   @Override
   public A_Entity toEntity(A_DTO dto) {
      return toEntity(dto, new HashMap<>());
   }

   @Override
   public A_DTO toDTO(A_Entity entity) {
      return toDTO(entity, new HashMap<>());
   }

   @Override
   public A_Entity toEntity(A_DTO dto, Map<Object, Object> cache) {
      // 检查缓存,已映射则直接返回
      if (cache.containsKey(dto)) {
         return (A_Entity) cache.get(dto);
      }
      A_Entity a = new A_Entity();
      cache.put(dto, a); // 先存入缓存,避免后续循环调用重复创建

      Set<M_Entity> ms = new HashSet<>();
      for (M_DTO m_dto : dto.getMs()) {
         // 传递缓存给M_Mapper
         ms.add(mMapper.toEntity(m_dto, cache));
      }
      a.setMs(ms);
      return a;
   }

   @Override
   public A_DTO toDTO(A_Entity entity, Map<Object, Object> cache) {
      if (cache.containsKey(entity)) {
         return (A_DTO) cache.get(entity);
      }
      A_DTO a = new A_DTO();
      cache.put(entity, a);

      Set<M_DTO> ms = new HashSet<>();
      for (M_Entity m_entity : entity.getMs()) {
         ms.add(mMapper.toDTO(m_entity, cache));
      }
      a.setMs(ms);
      return a;
   }
}

修改M_Mapper实现

同样添加缓存逻辑,复用A/C Mapper实例:

public class M_Mapper implements EntityDTO<M_Entity, M_DTO> {
   private final A_Mapper aMapper = new A_Mapper();
   private final C_Mapper cMapper = new C_Mapper();

   @Override
   public M_Entity toEntity(M_DTO dto) {
      return toEntity(dto, new HashMap<>());
   }

   @Override
   public M_DTO toDTO(M_Entity entity) {
      return toDTO(entity, new HashMap<>());
   }

   @Override
   public M_Entity toEntity(M_DTO dto, Map<Object, Object> cache) {
      if (cache.containsKey(dto)) {
         return (M_Entity) cache.get(dto);
      }
      M_Entity m = new M_Entity();
      cache.put(dto, m);

      if (dto.getA_DTO() != null) {
         m.setA_Entity(aMapper.toEntity(dto.getA_DTO(), cache));
      }
      if (dto.getC_DTO() != null) {
         m.setC_Entity(cMapper.toEntity(dto.getC_DTO(), cache));
      }
      return m;
   }

   @Override
   public M_DTO toDTO(M_Entity entity, Map<Object, Object> cache) {
      if (cache.containsKey(entity)) {
         return (M_DTO) cache.get(entity);
      }
      M_DTO m = new M_DTO();
      cache.put(entity, m);

      if (entity.getA_Entity() != null) {
         m.setA_DTO(aMapper.toDTO(entity.getA_Entity(), cache));
      }
      if (entity.getC_Entity() != null) {
         m.setC_DTO(cMapper.toDTO(entity.getC_Entity(), cache));
      }
      return m;
   }
}

C_Mapper的修改逻辑与A_Mapper完全一致,此处不再赘述。

方案2:仅映射关联ID,而非完整对象

如果业务不需要完整的关联对象,可简化DTO结构,只存储关联实体的ID,彻底避免循环映射。

修改M_DTO结构

public class M_DTO {
   private Long aId; // 仅存储A_Entity的ID
   private Long cId; // 仅存储C_Entity的ID
   // 其他业务属性...
   // getter/setter
}

修改M_Mapper实现

public class M_Mapper implements EntityDTO<M_Entity, M_DTO> {
   @Override
   public M_Entity toEntity(M_DTO dto) {
      M_Entity m = new M_Entity();
      // 此处可根据ID查询A/C实体,或仅设置ID(取决于JPA关联策略)
      return m;
   }

   @Override
   public M_DTO toDTO(M_Entity entity) {
      M_DTO m = new M_DTO();
      if (entity.getA_Entity() != null) {
         m.setAId(entity.getA_Entity().getId());
      }
      if (entity.getC_Entity() != null) {
         m.setCId(entity.getC_Entity().getId());
      }
      return m;
   }
}

此时A/C Mapper映射M_DTO时,不会触发A/C DTO的映射,彻底切断循环。


内容的提问来源于stack exchange,提问作者Romualdo Rubens de Freitas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 01:09:57