
在开发 restful api 或其他 spring boot 应用时,dto(data transfer object)通常用于在不同层之间传输数据,而领域模型(entity/model)则代表业务逻辑和数据库实体。在服务层中,我们经常需要将从客户端接收到的 dto 转换为实体进行持久化操作,或将从数据库查询到的实体转换为 dto 返回给客户端。
如果项目中存在数十个甚至更多的 DTO 和实体类,为每个 DTO-实体对编写单独的映射方法会产生大量的重复代码,例如:
// 在每个具体服务中重复编写的映射方法
@Resource(name = "modelMapper")
private final ModelMapper modelMapper;
private FaqDto mapToDto(Faq faq){
return modelMapper.map(faq, FaqDto.class);
}
private Faq mapToEntity(FaqDto faqDto){
return modelMapper.map(faqDto, Faq.class);
}这种模式不仅增加了代码量,也使得维护变得复杂。当映射规则发生变化时,可能需要在多个地方进行修改。因此,一个通用的、可复用的映射服务变得尤为必要。
为了解决上述问题,开发者通常会尝试引入泛型接口来抽象映射逻辑。例如,定义一个如下的 CommonService 接口:
public interface CommonService<T> {
T mapToEntity(T type);
T mapToDto(T type);
}并尝试实现它:
@Service
@Slf4j
@AllArgsConstructor
@Component("commonService")
public class CommonServiceImpl implements CommonService { // 注意:这里泛型丢失或不明确
@Resource(name = "modelMapper")
private final ModelMapper modelMapper;
@Override
public Object mapToEntity(Object type) {
// 尝试将所有类型映射到 Object.class,这会导致类型信息丢失
Object entityObject = modelMapper.map(type, Object.class);
return entityObject;
}
@Override
public Object mapToDto(Object type) {
// 尝试将所有类型映射到 Object.class,这会导致类型信息丢失
Object dtoObject = modelMapper.map(type, Object.class);
return dtoObject;
}
}然而,这种实现方式存在严重问题:ModelMapper.map() 方法需要明确的目标类型(即 Class<?> 参数)才能正确地进行映射。如果目标类型被指定为 Object.class,ModelMapper 无法知道具体要映射成哪种实体或 DTO,从而导致映射失败或返回不符合预期的 Object 实例,后续使用时需要强制类型转换,且容易引发 ClassCastException。
要实现一个真正类型安全的通用映射服务,我们需要在泛型接口中明确区分源类型和目标类型,并利用抽象类来存储目标类型的 Class 对象,供 ModelMapper 使用。
首先,定义一个泛型接口 CommonService,它接受两个泛型参数:E 代表实体类型(Entity),D 代表 DTO 类型。
// E for Entity and D for DTO
public interface CommonService<E, D> {
/**
* 将 DTO 对象映射为实体对象。
* @param dto 要映射的 DTO 对象。
* @return 映射后的实体对象。
*/
E mapToEntity(D dto);
/**
* 将实体对象映射为 DTO 对象。
* @param entity 要映射的实体对象。
* @return 映射后的 DTO 对象。
*/
D mapToDto(E entity);
}接下来,创建一个抽象类 AbstractCommonService 来实现 CommonService 接口。这个抽象类将负责管理 ModelMapper 实例以及泛型类型 E 和 D 对应的 Class 对象。
import org.modelmapper.ModelMapper;
public abstract class AbstractCommonService<E, D> implements CommonService<E, D> {
protected final ModelMapper modelMapper;
private final Class<E> entityClass; // 存储实体类的 Class 对象
private final Class<D> dtoClass; // 存储 DTO 类的 Class 对象
/**
* 构造函数,用于注入 ModelMapper 实例和具体的实体/DTO 类。
* @param modelMapper ModelMapper 实例
* @param entityClass 实体类的 Class 对象
* @param dtoClass DTO 类的 Class 对象
*/
public AbstractCommonService(ModelMapper modelMapper, Class<E> entityClass, Class<D> dtoClass) {
this.modelMapper = modelMapper;
this.entityClass = entityClass;
this.dtoClass = dtoClass;
}
@Override
public E mapToEntity(D dto) {
// 使用 ModelMapper 将 DTO 映射到具体的实体类
return modelMapper.map(dto, entityClass);
}
@Override
public D mapToDto(E entity) {
// 使用 ModelMapper 将实体映射到具体的 DTO 类
return modelMapper.map(entity, dtoClass);
}
}关键点说明:
现在,任何需要 DTO-实体映射的业务服务都可以继承 AbstractCommonService,并在构造函数中传入具体的实体类和 DTO 类。
假设我们有一个 SpecificEntity 和 SpecificDto:
// 示例实体类
public class SpecificEntity {
private Long id;
private String name;
// ... getters and setters
}
// 示例 DTO 类
public class SpecificDto {
private Long id;
private String name;
// ... getters and setters
}现在,我们可以这样创建 SpecificService:
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j; // 假设使用 Lombok
@Service
@Slf4j // 用于日志记录
public class SpecificService extends AbstractCommonService<SpecificEntity, SpecificDto> {
/**
* 构造函数,通过 Spring 自动注入 ModelMapper,并传入具体的实体和 DTO 类。
* @param modelMapper Spring 容器中配置的 ModelMapper 实例
*/
public SpecificService(ModelMapper modelMapper) {
super(modelMapper, SpecificEntity.class, SpecificDto.class);
}
// 可以在这里添加 SpecificService 独有的业务方法
public void performSpecificBusinessLogic() {
SpecificDto dto = new SpecificDto();
dto.setId(1L);
dto.setName("Test Item");
// 使用继承自 AbstractCommonService 的通用映射方法
SpecificEntity entity = this.mapToEntity(dto);
log.info("Mapped DTO to Entity: ID={}, Name={}", entity.getId(), entity.getName());
SpecificDto mappedDto = this.mapToDto(entity);
log.info("Mapped Entity to DTO: ID={}, Name={}", mappedDto.getId(), mappedDto.getName());
}
}通过这种方式,SpecificService 无需重复编写 mapToEntity 和 mapToDto 方法,直接继承并利用了抽象父类提供的通用映射逻辑,同时保持了类型安全。
为了使上述方案生效,您的 Spring Boot 应用中需要正确配置 ModelMapper bean。通常,这会在一个配置类中完成:
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
// 可以根据需要配置 ModelMapper 的匹配策略、跳过字段等
modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT) // 严格匹配策略
.setFieldMatchingEnabled(true) // 启用字段匹配
.setSkipNullEnabled(true); // 映射时跳过空值
return modelMapper;
}
}通过采用泛型接口和抽象类的组合,我们成功地构建了一个高度可复用且类型安全的 DTO 与实体映射服务。
优势:
注意事项:
这种通用映射模式是 Spring Boot 应用中处理 DTO 和实体转换的强大工具,能够显著提升代码质量和开发效率。
以上就是Spring Boot 中构建通用的 DTO 与实体映射服务的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号