Перейти к основному содержимому

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

· 4 мин. чтения

1. Обзор

В этом руководстве мы рассмотрим преобразования, которые должны происходить между внутренними объектами приложения Spring и внешними DTO (объектами передачи данных), которые публикуются обратно клиенту.

2. Сопоставитель модели

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

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

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

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

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

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

3. ДТО

Далее давайте представим сторону 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
}

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

  • Метод getSubmissionDateConverted() преобразует строку даты в дату в часовом поясе сервера, чтобы использовать ее в сохраняемом объекте Post .
  • Метод setSubmissionDate() должен установить дату DTO на дату публикации в часовом поясе текущего пользователя. ``

4. Сервисный уровень

Теперь давайте посмотрим на операцию на уровне обслуживания, которая, очевидно, будет работать с Entity (а не с 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();
}

Далее мы рассмотрим уровень над сервисом, уровень контроллера. Это где преобразование действительно произойдет.

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

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

Мы собираемся показать здесь несколько простых операций CRUD: создать, обновить, получить одну и получить все. Учитывая, что операции довольно просты, нас особенно интересуют аспекты преобразования 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);
}
}

Вот наше преобразование сущности Post в PostDto :

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

Вот преобразование из 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;
}

Итак, как мы видим, с помощью средства сопоставления моделей логика преобразования выполняется быстро и просто. Мы используем картографический API картографа и преобразуем данные, не написав ни одной строки логики преобразования.

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

Наконец, давайте проведем очень простой тест, чтобы убедиться, что преобразования между сущностью и 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. Заключение

В этой статье мы подробно описали упрощение преобразования из Entity в DTO и из DTO в Entity в Spring REST API с помощью библиотеки сопоставления моделей вместо написания этих преобразований вручную.

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