Entity To DTO Conversion for a Spring REST API – Spring REST API的实体到DTO的转换

最后修改: 2015年 8月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll handle the conversions that need to happen between the internal entities of a Spring application and the external DTOs (Data Transfer Objects) that are published back to the client.

在本教程中,我们将处理需要在Spring应用程序的内部实体和发布回客户端的外部DTOs(数据传输对象)之间发生的转换

2. Model Mapper

2.模型映射器

Let’s start by introducing the main library that we’re going to use to perform this entity-DTO conversion, ModelMapper.

让我们先介绍一下我们要用来执行这个实体-DTO转换的主要库,ModelMapper

We will need this dependency in the pom.xml:

我们将在pom.xml中需要这个依赖关系。

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

To check if there’s any newer version of this library, go here.

要检查这个库是否有更新的版本,到这里

Then we’ll define the ModelMapper bean in our Spring configuration:

然后我们将在Spring配置中定义ModelMapperbean。

@Bean
public ModelMapper modelMapper() {
    return new ModelMapper();
}

3. The DTO

3.DTO

Next let’s introduce the DTO side of this two-sided problem, Post DTO:

接下来让我们介绍一下这个两面性问题的DTO方面,Post DTO。

public class PostDto {
    private static final SimpleDateFormat dateFormat
      = new SimpleDateFormat("yyyy-MM-dd HH:mm");

    private Long id;

    private String title;

    private String url;

    private String date;

    private UserDto user;

    public Date getSubmissionDateConverted(String timezone) throws ParseException {
        dateFormat.setTimeZone(TimeZone.getTimeZone(timezone));
        return dateFormat.parse(this.date);
    }

    public void setSubmissionDate(Date date, String timezone) {
        dateFormat.setTimeZone(TimeZone.getTimeZone(timezone));
        this.date = dateFormat.format(date);
    }

    // standard getters and setters
}

Note that the two custom date related methods handle the date conversion back and forth between the client and the server:

请注意,这两个自定义的与日期有关的方法处理客户端和服务器之间来回的日期转换。

  • getSubmissionDateConverted() method converts date String into a Date in the server’s timezone to use it in the persisting Post entity
  • setSubmissionDate() method is to set DTO’s date to Post‘s Date in current user timezone

4. The Service Layer

4.服务层

Now let’s look at a service level operation, which will obviously work with the Entity (not the DTO):

现在让我们看看一个服务级操作,它显然将与实体(而不是DTO)一起工作。

public List<Post> getPostsList(
  int page, int size, String sortDir, String sort) {
 
    PageRequest pageReq
     = PageRequest.of(page, size, Sort.Direction.fromString(sortDir), sort);
 
    Page<Post> posts = postRepository
      .findByUser(userService.getCurrentUser(), pageReq);
    return posts.getContent();
}

We’re going to have a look at the layer above service next, the controller layer. This is where the conversion will actually happen.

我们接下来要看一下服务之上的那一层,即控制器层。这就是实际发生转换的地方。

5. The Controller Layer

5.控制器层

Next let’s examine a standard controller implementation, exposing the simple REST API for the Post resource.

接下来让我们来看看一个标准的控制器实现,为Post资源暴露简单的REST API。

We’re going to show here a few simple CRUD operations: create, update, get one, and get all. Given that the operations are pretty straightforward, we are especially interested in the Entity-DTO conversion aspects:

我们将在这里展示几个简单的CRUD操作:创建、更新、获取一个和获取全部。鉴于这些操作非常简单明了,我们对实体-DTO转换方面特别感兴趣

@Controller
class PostRestController {

    @Autowired
    private IPostService postService;

    @Autowired
    private IUserService userService;

    @Autowired
    private ModelMapper modelMapper;

    @GetMapping
    @ResponseBody
    public List<PostDto> getPosts(...) {
        //...
        List<Post> posts = postService.getPostsList(page, size, sortDir, sort);
        return posts.stream()
          .map(this::convertToDto)
          .collect(Collectors.toList());
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    @ResponseBody
    public PostDto createPost(@RequestBody PostDto postDto) {
        Post post = convertToEntity(postDto);
        Post postCreated = postService.createPost(post));
        return convertToDto(postCreated);
    }

    @GetMapping(value = "/{id}")
    @ResponseBody
    public PostDto getPost(@PathVariable("id") Long id) {
        return convertToDto(postService.getPostById(id));
    }

    @PutMapping(value = "/{id}")
    @ResponseStatus(HttpStatus.OK)
    public void updatePost(@PathVariable("id") Long id, @RequestBody PostDto postDto) {
        if(!Objects.equals(id, postDto.getId())){
            throw new IllegalArgumentException("IDs don't match");
        }
        Post post = convertToEntity(postDto);
        postService.updatePost(post);
    }
}

Here is our conversion from Post entity to PostDto:

下面是我们从Post实体到PostDto的转换:

private PostDto convertToDto(Post post) {
    PostDto postDto = modelMapper.map(post, PostDto.class);
    postDto.setSubmissionDate(post.getSubmissionDate(), 
        userService.getCurrentUser().getPreference().getTimezone());
    return postDto;
}

Here is the conversion from DTO to an entity:

这里有一个从DTO到实体的转换

private Post convertToEntity(PostDto postDto) throws ParseException {
    Post post = modelMapper.map(postDto, Post.class);
    post.setSubmissionDate(postDto.getSubmissionDateConverted(
      userService.getCurrentUser().getPreference().getTimezone()));
 
    if (postDto.getId() != null) {
        Post oldPost = postService.getPostById(postDto.getId());
        post.setRedditID(oldPost.getRedditID());
        post.setSent(oldPost.isSent());
    }
    return post;
}

So as we can see, with the help of the model mapper, the conversion logic is quick and simple. We’re using the map API of the mapper, and getting the data converted without writing a single line of conversion logic.

所以我们可以看到,在模型映射器的帮助下,转换逻辑是快速而简单的。我们使用映射器的map API,并在不写一行转换逻辑的情况下获得数据转换。

6. Unit Testing

6.单元测试

Finally, let’s do a very simple test to make sure the conversions between the entity and the DTO work well:

最后,让我们做一个非常简单的测试,确保实体和DTO之间的转换工作顺利。

public class PostDtoUnitTest {

    private ModelMapper modelMapper = new ModelMapper();

    @Test
    public void whenConvertPostEntityToPostDto_thenCorrect() {
        Post post = new Post();
        post.setId(1L);
        post.setTitle(randomAlphabetic(6));
        post.setUrl("www.test.com");

        PostDto postDto = modelMapper.map(post, PostDto.class);
        assertEquals(post.getId(), postDto.getId());
        assertEquals(post.getTitle(), postDto.getTitle());
        assertEquals(post.getUrl(), postDto.getUrl());
    }

    @Test
    public void whenConvertPostDtoToPostEntity_thenCorrect() {
        PostDto postDto = new PostDto();
        postDto.setId(1L);
        postDto.setTitle(randomAlphabetic(6));
        postDto.setUrl("www.test.com");

        Post post = modelMapper.map(postDto, Post.class);
        assertEquals(postDto.getId(), post.getId());
        assertEquals(postDto.getTitle(), post.getTitle());
        assertEquals(postDto.getUrl(), post.getUrl());
    }
}

7. Conclusion

7.结论

In this article, we detailed simplifying the conversion from Entity to DTO, and from DTO to Entity in a Spring REST API, by using the model mapper library instead of writing these conversions by hand.

在这篇文章中,我们详细介绍了简化Spring REST API中从Entity到DTO以及从DTO到Entity的转换,通过使用模型映射器库而不是手工编写这些转换。

The full source code for the examples is available in the GitHub project.

示例的完整源代码可在GitHub项目中获得。