Entity To DTO Conversion for a Spring REST API

一世执手 提交于 2020-02-25 15:24:08

1. Overview

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.

2. Model Mapper

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

We will need this dependency in the pom.xml:

1
2
3
4
5
< dependency >
     < groupId >org.modelmapper</ groupId >
     < artifactId >modelmapper</ artifactId >
     < version >2.3.5</ version >
</ dependency >

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

We'll then define the ModelMapper bean in our Spring configuration:

1
2
3
4
public ModelMapper modelMapper() {
     return new ModelMapper();
}

3. The DTO

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 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

Let's now look at a service level operation – which will obviously work with the Entity (not the DTO):

1
2
3
4
5
6
7
8
9
10
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 as well.

5. The Controller Layer

Let's now have a look at a standard controller implementation, exposing the simple REST API for the Post resource.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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( @RequestBody PostDto postDto) {
         Post post = convertToEntity(postDto);
         postService.updatePost(post);
     }
}

And here is our conversion from Post entity to PostDto:

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

And here is the conversion from DTO to an entity:

1
2
3
4
5
6
7
8
9
10
11
12
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 you 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.

6. Unit Testing

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

This was an article on 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.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!