将 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 合并到数据库