如何避免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
相关产品推荐
相关产品推荐

