如何在Spring Boot中编写通用服务实现DTO与Model互转?
问题分析
你在Spring Boot电商REST API开发中,面临十多个Model(Entity)和DTO类的映射重复代码问题,尝试的通用服务因泛型设计不合理,导致使用时需要类型转换且运行报错。
你的当前实现中,每个Service都重复编写类似mapToDto/mapToEntity的方法;而尝试的CommonService使用单一泛型T,无法区分Entity和DTO类型,实现类返回Object且错误地映射到Object.class,这是导致问题的核心原因。
解决方案
下面提供两种实用的通用映射方案,避免重复代码且无需手动类型转换:
方案一:泛型继承式通用服务
通过定义带双泛型的接口,让具体Service继承泛型实现类,直接复用映射方法:
1. 定义通用映射接口
import java.util.List; public interface CommonMappingService<E, D> { // Entity转DTO D mapToDto(E entity); // DTO转Entity E mapToEntity(D dto); // 批量Entity转DTO List<D> mapToDtoList(List<E> entityList); // 批量DTO转Entity List<E> mapToEntityList(List<D> dtoList); }
2. 实现泛型通用服务
import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; import java.lang.reflect.ParameterizedType; import java.util.List; import java.util.stream.Collectors; @Service public class CommonMappingServiceImpl<E, D> implements CommonMappingService<E, D> { private final ModelMapper modelMapper; public CommonMappingServiceImpl(ModelMapper modelMapper) { this.modelMapper = modelMapper; } @Override @SuppressWarnings("unchecked") public D mapToDto(E entity) { // 通过反射获取泛型参数中的DTO类型 Class<D> dtoClass = (Class<D>) ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[1]; return modelMapper.map(entity, dtoClass); } @Override @SuppressWarnings("unchecked") public E mapToEntity(D dto) { // 通过反射获取泛型参数中的Entity类型 Class<E> entityClass = (Class<E>) ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[0]; return modelMapper.map(dto, entityClass); } @Override public List<D> mapToDtoList(List<E> entityList) { return entityList.stream() .map(this::mapToDto) .collect(Collectors.toList()); } @Override public List<E> mapToEntityList(List<D> dtoList) { return dtoList.stream() .map(this::mapToEntity) .collect(Collectors.toList()); } }
3. 具体Service继承复用
import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; @Service public class FaqService extends CommonMappingServiceImpl<Faq, FaqDto> { private final FaqRepository faqRepository; // 构造函数注入ModelMapper和Repository,父类会处理ModelMapper public FaqService(ModelMapper modelMapper, FaqRepository faqRepository) { super(modelMapper); this.faqRepository = faqRepository; } // 业务方法示例 public FaqDto getFaqById(Long id) { Faq faq = faqRepository.findById(id) .orElseThrow(() -> new RuntimeException("FAQ不存在")); // 直接调用父类的映射方法,无需重复编写 return mapToDto(faq); } }
方案二:独立通用映射工具类
如果不想让Service继承父类,可以封装一个工具类,通过传入Class参数实现类型安全的映射:
1. 实现映射工具类
import org.modelmapper.ModelMapper; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Component public class MappingUtils { private final ModelMapper modelMapper; public MappingUtils(ModelMapper modelMapper) { this.modelMapper = modelMapper; } // Entity转DTO public <E, D> D mapToDto(E entity, Class<D> dtoClass) { return modelMapper.map(entity, dtoClass); } // DTO转Entity public <E, D> E mapToEntity(D dto, Class<E> entityClass) { return modelMapper.map(dto, entityClass); } // 批量Entity转DTO public <E, D> List<D> mapToDtoList(List<E> entityList, Class<D> dtoClass) { return entityList.stream() .map(entity -> mapToDto(entity, dtoClass)) .collect(Collectors.toList()); } // 批量DTO转Entity public <E, D> List<E> mapToEntityList(List<D> dtoList, Class<E> entityClass) { return dtoList.stream() .map(dto -> mapToEntity(dto, entityClass)) .collect(Collectors.toList()); } }
2. 具体Service注入使用
import org.springframework.stereotype.Service; @Service public class FaqService { private final FaqRepository faqRepository; private final MappingUtils mappingUtils; public FaqService(FaqRepository faqRepository, MappingUtils mappingUtils) { this.faqRepository = faqRepository; this.mappingUtils = mappingUtils; } public FaqDto getFaqById(Long id) { Faq faq = faqRepository.findById(id) .orElseThrow(() -> new RuntimeException("FAQ不存在")); // 调用工具类方法,传入目标DTO类型 return mappingUtils.mapToDto(faq, FaqDto.class); } }
为什么你的原有方案会报错
- 泛型设计错误:单一泛型
T无法区分Entity和DTO两种不同类型,导致映射逻辑混乱; - 映射目标错误:实现类中
modelMapper.map(type, Object.class)会把对象映射成Object类型,丢失原有属性结构,自然会导致接口返回数据异常或报错; - 类型转换问题:返回
Object类型需要手动强转,不仅繁琐还容易出现类型转换异常。
内容的提问来源于stack exchange,提问作者Manir Mahamat
相关产品推荐
相关产品推荐

