将 DTO 转换为实体,反之亦然

Posted

技术标签:

【中文标题】将 DTO 转换为实体,反之亦然【英文标题】:Conversion of DTO to entity and vice-versa 【发布时间】:2015-04-26 12:35:53 【问题描述】:

我在我的 Web 应用程序中使用 Spring MVC 架构和 JPA。在哪里手动(即不使用任何框架)将数据传输对象 (DTO) 转换为 JPA 实体,反之亦然?

【问题讨论】:

【参考方案1】:

我建议另一种没有额外依赖的方法:

import org.springframework.beans.BeanUtils
...
BeanUtils.copyProperties(sourceObject, targetObject);

可用于将 DTO 转换为实体,反之亦然,前提是它们具有相同的属性类型和名称。

如果您想忽略某些字段,只需在targetObject 之后添加它们即可。

BeanUtils.copyProperties(sourceObj, targetObj, "propertyToIgnoreA", "propertyToIgnoreB", "propertyToIgnoreC");

来源:http://appsdeveloperblog.com/dto-to-entity-and-entity-to-dto-conversion/

我认为这是最干净的方式。记得查看 Javadoc 中的警告!

【讨论】:

我喜欢这个答案。在新的世界秩序中,除非字段名称不同,否则这似乎是无需编写任何样板的完美方式。 感谢您提供明智的答案。 虽然BeanUtils.copyProperties 是一个可行的解决方案,但它仍然基于反射 API,因此与基于构建时代码生成的 MapStruct 和 Orika 等工具相比,每次转换都会产生相当大的开销。 MapStruct 非常简单,我建议在性能很重要的情况下使用它而不是 BeanUtils 是的,这就是我提到的“警告”……还有其他的,所以 Javadoc 是必读的。 性能问题我没用过,第一次用会很贵【参考方案2】:

使用 mapstruct 库。另外在 build.gradle 中添加了以下内容

sourceSets 
    main.java.srcDirs += "build/generated/sources/annotationProcessor/java/main/"

【讨论】:

【参考方案3】:

我可以推荐使用mapstruct 库:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>1.2.0.Final</version>
</dependency>

例如,如果你有这样一个实体:

public class Entity 
    private Integer status;
    private String someString;
    private Date startDate;
    private Date endDate;

    // SKIPPED

还有 DTO:

public class Dto 
    private Boolean status;
    private String someString;
    private Long startDate;
    private Long endDate;

    // SKIPPED

那么就可以在服务层通过这种方式进行转换:

@Service
public class SomeServiceImpl implements SomeService 

    @Autowired
    SomeDao someDao;

    @Autowired
    SomeMapper someMapper;


    public Dto getSomething(SomeRequest request) throws SomeException 
        return someDao.getSomething(request.getSomeData())
                .map(SomeMapper::mapEntityToDto)
                .orElseThrow(() -> new SomeException("..."));
    

Mapper可以表示如下:

@Mapper 
public interface SomeMapper 
    @Mappings(
            @Mapping(target = "entity", 
                      expression = "java(entity.getStatus() == 1 ? Boolean.TRUE : Boolean.FALSE)"),
             @Mapping(target = "endDate", source = "endDate"),
             @Mapping(target = "startDate", source = "startDate")
            )

    Dto mapEntityToDto(Entity entity);

【讨论】:

除了使用 MapStruct 我还推荐使用这个扩展 github.com/Pozo/mapstruct-kotlin 这允许你保持你的 kotlin 数据类不可变,不再需要你显式声明一个无参数 ctor 即构造函数( ) : this(null, null)【参考方案4】:

这是一个已接受答案的老问题,但尽管使用模型映射器 API 以简单的方式更新它。

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>0.7.4</version>
</dependency>

使用此 API,您可以避免手动设置器和获取器,如已接受答案中所述。

在我看来,两种转换都应该在控制器上进行,借助私有实用方法并使用 Java8 流的映射(如果交换了 DTO 集合),如 this article 中所示。

它应该发生在控制器上,因为 DTO 是独占传输对象。我不会进一步降低我的 DTO。

您在实体上编写服务和数据访问层,并在调用服务方法之前将 DTO 转换为实体,并在从控制器返回响应之前将实体转换为 DTO。

我更喜欢这种方法,因为实体很少更改,并且可以根据需要在 DTO 中添加/删除数据。

详细的模型映射器配置和规则描述here

【讨论】:

在我看来,控制器只是控制输入和输出数据。转换过程必须发生在服务类中。另一件事,这个modelmapper,它使用反射来执行转换吗? @ThiagoCunha:我没有看过 modelmapper 源代码,但反射对我来说似乎是唯一的方法。另一方面,由于控制器负责控制输入/输出数据(根据您),所以逻辑上的转换也应该在那里发生。实际上转换代码可以放在一些实用程序类中,但转换调用自然应该从控制器发生,尽管有没有硬性规定。这些只是意见。 所以我更喜欢在仍然处于古老模式的情况下创建一个设计模式(笑声)。这是因为反射在编译时不会经过优化过程。我通过通用代码生成模型使用了转换器设计模式。我应用了一项不错的技术,尽管需要手动操作,但它还是很有成效的。 @ThiagoCunha:嗨,我建议先检查模型映射器代码,以确保它真的使用反射,因为性能非常好。市场上的 API 很少有相同的,并且可能会使您免于手动工作。【参考方案5】:

我想你是在问在哪里写整个实体-->DTO 转换逻辑。

喜欢你的实体

class StudentEntity 
 int age ;
 String name;

 //getter
 //setter

 public StudentDTO _toConvertStudentDTO()
    StudentDTO dto = new StudentDTO();
    //set dto values here from StudentEntity
    return dto;
 


你的 DTO 应该是这样的

class StudentDTO  
 int age ;
 String name;

 //getter
 //setter

 public StudentEntity _toConvertStudentEntity()
    StudentEntity entity = new StudentEntity();
    //set entity values here from StudentDTO
    return entity ;
 


你的控制器应该是这样的

@Controller
class MyController 

    public String my()

    //Call the conversion method here like
    StudentEntity entity = myDao.getStudent(1);
    StudentDTO dto = entity._toConvertStudentDTO();

    //As vice versa

    


【讨论】:

如果人们关心模块化,这种方法将迫使 DTO 依赖实体,反之亦然。此外,在多模块项目中,如果您的实体和 DTO 在单独的模块中(通常是这种情况),这将导致循环依赖。结帐this answer DTO 是不应该包含任何业务逻辑的简单对象,即它们不应该知道实体(值对象)或如何将自己转换为实体(值对象)。 您的回答违反了 OOP 原则。 谈到干净的代码,我相信转换过程将在服务内,而不是在应用程序控制层。还有一点需要注意的是,还是和DDD有关的,就是有一个业务规则涉及到你的实体,而实体只有一个原则,并没有增加业务价值。 Martin (2013) 在他关于 DTO 的干净代码书中也指出了这一点。阅读如何使用它。起初,我很痛苦,将转换放入 DTO,而实际上它需要在该业务的特定转换类中。 1. DTO 不应该有任何逻辑。 2. DTO 不应该知道实体,反之亦然。 3. 你打破了 SRP【参考方案6】:

在我看来

实体 -> DTO 转换应在调度 jsp 页面之前在控制器中完成 验证从 jsp 页面返回的 DTO 后,也应在 Controller 中完成 DTO -> 实体转换

它使您可以更好地控制流程,并且您不必在每次更改填充实体的某些逻辑时都更改服务/持久性类。

【讨论】:

同意。此外,如果您在 Entity 和 DTO 中组合转换逻辑,则无法将两者分离为单独的 JAR 工件。例如,如果不包含实体,就不能将 DTO 类提供给第三方,这(在大多数情况下)是您不希望这样做的。 如果您的实体中有延迟加载属性怎么办?如果您在控制器中进行转换,则将加载属性,这可能是不希望的。我们是否应该为领域对象增加一层? (这可能很昂贵)

以上是关于将 DTO 转换为实体,反之亦然的主要内容,如果未能解决你的问题,请参考以下文章

JPA的模式:从实体生成数据传输对象DTO并将DTO合并到数据库

JPA 模式:从实体生成数据传输对象 DTO 并将 DTO 合并到数据库

领域实体、DTO 和视图模型

将 HTML 实体转换为 Unicode,反之亦然

在 TypeScript 和 NestS 中将类转换为类/对象(实体到 DTO)

具有业务对象、DTO 和实体/域对象的数据转换模式