Преобразование сущности в DTO для Spring REST API

Преобразование сущности в DTO для Spring REST API

1. обзор

В этом руководстве мы будем обрабатывать необходимые преобразованияbetween the internal entities of a Spring application and the external DTOs (объекты передачи данных), которые публикуются обратно для клиента.

Дальнейшее чтение:

Руководство по составлению карт с помощью Dozer

Dozer - это преобразователь Java Bean в Java Bean, который копирует данные из одного объекта в другой, атрибут за атрибутом, поддерживает сопоставление имен атрибутов, выполняет преобразование типов и многое другое.

Read more

Краткое руководство по MapStruct

Краткое и практическое руководство по использованию MapStruct

Read more

2. Модель Mapper

Давайте начнем с представления основной библиотеки, которую мы собираемся использовать для выполнения преобразования сущности в DTO -ModelMapper.

Нам понадобится эта зависимость вpom.xml:


    org.modelmapper
    modelmapper
    2.3.2

Чтобы проверить, есть ли более новая версия этой библиотеки,go here.

Затем мы определим bean-компонентModelMapper в нашей конфигурации Spring:

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

3. 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
}

Обратите внимание, что 2 пользовательских метода, связанных с датами, обрабатывают преобразование даты между клиентом и сервером:

  • МетодgetSubmissionDateConverted() преобразует строку даты в дату в часовом поясе сервера, чтобы использовать ее в сохраняющейся сущности Post.

  • МетодsetSubmissionDate() заключается в установке даты DTO на дату публикации в часовом поясе текущего пользователя.

4. Уровень обслуживания

Давайте теперь посмотрим на операцию уровня обслуживания, которая, очевидно, будет работать с Entity (а не с DTO):

public List getPostsList(
  int page, int size, String sortDir, String sort) {

    PageRequest pageReq
     = PageRequest.of(page, size, Sort.Direction.fromString(sortDir), sort);

    Page posts = postRepository
      .findByUser(userService.getCurrentUser(), pageReq);
    return posts.getContent();
}

Далее мы посмотрим на слой над сервисом - уровень контроллера. Вот где на самом деле произойдет конверсия.

5. Уровень контроллера

Теперь давайте посмотрим на стандартную реализацию контроллера, демонстрирующую простой REST API для ресурсаPost.

Мы собираемся показать здесь несколько простых операций CRUD: создать, обновить, получить одно и получить все. И учитывая, что операции довольно просты,we are especially interested in the Entity-DTO conversion aspects:

@Controller
class PostRestController {

    @Autowired
    private IPostService postService;

    @Autowired
    private IUserService userService;

    @Autowired
    private ModelMapper modelMapper;

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public List getPosts(...) {
        //...
        List 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);
    }
}

А вот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;
}

А вот преобразованиеfrom DTO to an 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;
}

Итак, как вы можете видеть, с помощью сопоставителя моделиthe conversion logic is quick and simple - мы используем APImap сопоставителя и преобразуем данные без написания ни одной строчки логики преобразования.

6. Модульное тестирование

Наконец, давайте проведем очень простой тест, чтобы убедиться, что преобразования между сущностью и DTO работают правильно:

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

7. Заключение

Это была статья оsimplifying the conversion from Entity to DTO and from DTO to Entity in a Spring REST API, в которой использовалась библиотека сопоставления моделей вместо того, чтобы писать эти преобразования вручную.

Полный исходный код примеров доступен вGitHub project.