Học viên: Lương Quốc
Lớp: Java Fulltack 15
Email: quoc200799@gmail.com
Link tham khảo: https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application


1. Tổng quan

Trong hướng dẫn này, chúng ta sẽ xử lý các chuyển đổi cần xảy ra giữa các thực thể bên trong của ứng dụng Spring và các DTOs (Đối tượng truyền dữ liệu) được xuất trở lại client.

2. Model Mapper

Hãy bắt đầu bằng cách giới thiệu thư viện chính mà chúng ta sẽ sử dụng để thực hiện chuyển đổi entity-DTO, ModelMapper.
Chúng ta sẽ cần dependency này trong file pom.xml:

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

Kiểm tra nếu có bất kì phiên bản nào mới hơn tại thư viện này, Tại đây.
Sau đó chúng ta sẽ định nghĩa ModelMapper trong bean tại cấu hình của Spring

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

3. DTO

Tiếp theo hãy đưa DTO vào 2 mặt này tại PostDTO:

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);
    }

}

Lưu ý rằng hai phương thức liên quan đến ngày tùy chỉnh xử lý chuyển đổi ngày qua lại giữa client và server:

  • Phương thức getSubmissionDateConverted() chuyển đổi thời gian từ String thành thời gian của server được sử dụng nó liên tục tại entity Post.
  • Phương thức setSubmissionDate() là thay thời gian của DTO thành thời gian của Post của user hiện tại.

4. Lớp Service

Bây giờ hãy xem các mức độ vận hành của service, Nó sẽ làm việc rõ ràng với Entity (không với 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();
}

Chúng ta sẽ xem xét lớp trên dịch vụ tiếp theo, trên các lớp controller. Đây là nơi chuyển đổi sẽ thực sự xảy ra.

5. Lớp Controller

Tiếp theo hãy thực hiện một tiêu chuẩn của controller, hiển thị API REST đơn giản cho tài nguyên Post.
Chúng ta sẽ trình bày ở đây một số thao tác CRUD đơn giản: tạo, cập nhật, nhận một và nhận tất cả. Cho rằng các hoạt động là khá đơn giản, chúng ta đặc biệt quan tâm đến các khía cạnh chuyển đổi Entity-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);
    }
}

Đây là chuyển đổi từ entity Post thành PostDto:

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

Còn đây là chuyển đổi từ DTO thành 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;
}

Vì vậy như chúng ta có thể thấy, với sự trợ giúp của model mapper, logic chuyển đổi rất nhanh chóng và đơn giản. Chúng ta đang sử dụng map API của trình mapper, và chuyển đổi dữ liệu mà không cần viết một dòng logic chuyển đổi nào.

6. Đơn vị testing

Cuối cùng, hãy làm một kiểm tra thật đơn giản bảo đảm việc chuyển đổi giữa entity và 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. Tổng kết

Trong bài viết này, chúng tôi đã trình bày chi tiết việc đơn giản hóa việc chuyển đổi từ Entity sang DTO và từ DTO sang Entity trong Spring REST API, bằng cách sử dụng thư viện model mapper thay vì viết các chuyển đổi này bằng tay.
Mã nguồn đầy đủ cho các ví dụ có sẵn trong Github Project