introduce
In this article, we will deal with the transformations that need to be done between the internal entities of Spring applications and DTO s (data transfer objects) outside the client.
Model mapping
Let's start by introducing the main library of entity to DTO transformation for execution - ModelMapper. We need to add the following dependencies to pom.xml:
<dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>2.3.2</version> </dependency>
Then, we define the ModelMapper bean in the Spring configuration:
@Bean public ModelMapper modelMapper() { return new ModelMapper(); }
DTO
Next, let's introduce the DTO aspect of this two-sided problem - 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); } // getters and setters }
Note that two custom date related methods handle date round-trip conversion between client and server:
- The getSubmissionDateConverted() method converts the Date string to a Date in the server time zone to use it in the persisted Post entity
- The setSubmissionDate() method is used to set the Date of DTO to the Date of Post in the current user's time zone
Service
Now let's look at the operation of a Service layer -- obviously it will work with entities (not DTO s):
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(); }
Next, we will see the upper layer of the service - Controller layer, which is also where the transformation actually occurs.
Controller
Now let's look at a standard Controller implementation that exposes a simple REST API for Post.
Here we show a few simple CRUD operations: create, update, get one, and get all.
@Controller class PostRestController { @Autowired private IPostService postService; @Autowired private IUserService userService; @Autowired private ModelMapper modelMapper; @RequestMapping(method = RequestMethod.GET) @ResponseBody public List<PostDto> getPosts(...) { //... List<Post> posts = postService.getPostsList(page, size, sortDir, sort); return posts.stream() .map(post -> convertToDto(post)) .collect(Collectors.toList()); } @RequestMapping(method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) @ResponseBody public PostDto createPost(@RequestBody PostDto postDto) { Post post = convertToEntity(postDto); Post postCreated = postService.createPost(post)); return convertToDto(postCreated); } @RequestMapping(value = "/{id}", method = RequestMethod.GET) @ResponseBody public PostDto getPost(@PathVariable("id") Long id) { return convertToDto(postService.getPostById(id)); } @RequestMapping(value = "/{id}", method = RequestMethod.PUT) @ResponseStatus(HttpStatus.OK) public void updatePost(@RequestBody PostDto postDto) { Post post = convertToEntity(postDto); postService.updatePost(post); } }
This is our conversion from Post entity to PostDto:
private PostDto convertToDto(Post post) { PostDto postDto = modelMapper.map(post, PostDto.class); postDto.setSubmissionDate(post.getSubmissionDate(), userService.getCurrentUser().getPreference().getTimezone()); return postDto; }
This is the transformation from DTO to entity:
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; }
Therefore, as you can see, with the help of model mapping, the transformation logic is fast and simple -- we can get the transformed data without writing any transformation logic by using the map API of mapping.
unit testing
Finally, let's do a very simple test to ensure that the transformation between entity and DTO works properly:
public class PostDtoUnitTest { private ModelMapper modelMapper = new ModelMapper(); @Test public void whenConvertPostEntityToPostDto_thenCorrect() { Post post = new Post(); post.setId(Long.valueOf(1)); 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(Long.valueOf(1)); 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()); } }
summary
This is an article about simplifying entity to DTO and DTO to entity transformations in the Spring REST API by using the model mapping library instead of writing these transformations manually.
Welcome to my public address: Qu Ling Feng, get exclusive exclusive learning resources and daily dry goods push.
If you are interested in my topic content, you can also follow my blog: sagowiec.com