控制層使用DTO代替Entity

 

本文是關於如何在api請求體和響應體中使用DTO,以及如何在DTO和實體之間進行映射。

爲什麼使用DTO而不是實體?

  • 更新實體有時只更新實體的一部分,如果在請求體中使用實體,當您只傳遞實體中的部分字段時,其他字段將是實體的默認值或null。如果實體的字段太多,更新將變得非常麻煩。
  • 通過使用僅傳遞您需要的字段的不同DTO,它將使請求更加高效和簡潔。
  • Spring MVC自動將請求參數綁定到bean,聲明爲使用@RequestMapping註釋的方法的參數。由於這種自動綁定功能,可以在@RequestMapping帶註釋的方法的參數上提供一些意外的字段。參考自:https://rules.sonarsource.com/java/tag/spring/RSPEC-4684。 

你什麼時候需要DTO?

通常我們需要在創建,更新實體時創建DTO或在api中返回一些json數據。

 1.創建一個實體

創建實體時,我們需要創建一個DTO來重現字段。在大多數情況下,DTO包含所有字段的實體,並且每個實體應該只有一個創建DTO。

我們應該只包含DTO中需要的字段,並將所有需要字段傳遞給DTO而不使用空值。 

例如,當您創建job時,如果創建job操作僅創建具有一些基本信息的作業,您只需要從請求傳遞必需的字段並創建DTO來處理傳遞的字段,

所以DTO只包含創建實體所必需的字段。在DTO中,使用驗證來驗證字段的完整性。例如

public class JobDto {
 
  private Long jobId;
 
  @NotBlank(message = "The job title couldn't be empty!")
  private String title;
 
  @NotBlank(message = "The job employment type couldn't be empty!")
  private String employmentType;
  ...
}

使用@Valid註釋來驗證它,如果驗證沒有通過,它將拋出異常:

@PostMapping(value = "/jobs")
@PreAuthorize("hasAuthority('create_job')")
public JobDetail createJob(@RequestBody @Valid final JobDto job) {
...
}

2.更新實體

更新實體時,如果更新是完全更新,我們可以使用在步驟1中創建的實體。如果更新是部分更新,我們需要創建新端點以進行更新並創建新的DTO以重新獲取字段。

例如,當我們需要更新job的描述,但我們不想傳遞job的所有字段時,我們應該添加一個端點來處理更新,並創建一個DTO來重新接收該字段。例如

@Data
@NoArgsConstructor
public class JobDescriptionUpdatePojo {
  private String description;
}
 
@PatchMapping("jobs/{id}/job-description")
@PreAuthorize("hasPermission(#jobId,'change_job')")
public JobInfoDto updateJobDescription(@PathVariable final Long id,
    @RequestBody final JobDescriptionUpdatePojo jobDescriptionUpdatePojo) {
  final Job job = jobService.findJobById(id);
  return jobMapper.convertToJobInfoDto(jobService
      .updateJobDescription(JobDescriptionUpdatePojo, job));
}

3.返回json數據

當我們在api中返回json數據時,我們需要創建一個DTO來返回數據,而DTO應該只包含我們需要的字段。

使用MapStruct進行映射,我們需要將依賴項添加到項目的pom.xml文件中:

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

< dependency > 
  < groupId > org.mapstruct </ groupId > 
  < artifactId > mapstruct-processor </ artifactId > 
  < version >1.3.0.Final</ version > 
</ dependency >

配置映射器

這是所有映射器的全局配置,如果你真的需要使用不同的配置,請創建另一個。

@MapperConfig(

    componentModel = "spring",

    injectionStrategy = InjectionStrategy.CONSTRUCTOR,

    nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,

    mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG)

public interface Config {

 

  @Mapping(target = "id", ignore = true)

  BaseEntity convertToEntity(Object dto);

}

Config接口爲mapper提供了一些基本配置。

  • 使用構造注入來注入映射器。
  • 對於映射,將忽略空值,僅對@MappingTarget註釋有效。
  • 使任何DTO轉換爲實體忽略id字段。

用於映射的接口

一個實體對應一個映射器,定義以下方法以區分映射器的不同用法:

  • createFrom {DTOName}:將DTO轉換爲新實體,執行映射時將忽略“id”字段,因爲它是最常見的用例作爲主鍵。
  • updateFrom {DTOName}:將DTO轉換爲現有實體,實體param是存在的實體,此方法將用DTO的同名字段替換實體的字段。
  • convertToDto:使用相同的字段將實體轉換爲DTO。

@Mapper(config = Config.class)

public interface {Entity}Mapper {

 

  Entity createFrom{DTO1}(DTO1 dto1);

 

  void updateFrom{DTO2}(@MappingTarget Entity entity, DTO2 dto2);

 

  DTO3 convertTo{DTO3}(Entity entity);

 

  DTO4 convertTo{DTO4}(Entity entity);

 

  ...

}

映射器示例

  • 將@Mapper註釋與您的自定義配置一起使用並根據需要導入值,MapStruct將自動生成接口的實現,就像Spring中的存儲庫一樣。
  • 使用@Mapping註釋來配置方法,註釋爲目標字段值提供表達式。
  • 使用@Mapping(target =“targetField”,source =“sourceField”)來指定映射字段,如果字段名稱相同,則無需配置,它將自動映射。

@Mapper(

    config = Config.class,

    imports = {ClassNeedInExpression.class})

public interface JobMapper {

 

  @Mapping(target = "title", source = "jobTitle")

  @Mapping(target = "status", constant = "DRAFTING")

  @Mapping(

      target = "other field",

      expression = "java("some java code")"

      )

  Job createFromJobDto(JobDto jobDto);

 

  JobInfoDto convertToJobInfoDto(Job job);

}

示例使用映射器

  • 使用構造注入來注入您需要的映射器
  • 使用mapper轉換實體或DTO

public JobInfoDto createJob(@RequestBody @Valid final JobDto job) {

  final Job persistentJob = jobMapper.createFromJobDto(job);

  // do other things with the converted job

  ...

  final Job newJob = jobService.save(persistentJob);

  // convert the new Job to return DTO

 

  final JobInfoDto jobDto = jobMapper.convertToJobInfoDto(newJob);

  return jobDto;

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章