Conversion d’entité en DTO pour une API REST Spring

Conversion d'entité vers DTO pour une API REST Spring

1. Vue d'ensemble

Dans ce didacticiel, nous allons gérer les conversions qui doivent se produirebetween the internal entities of a Spring application and the external DTOs (objets de transfert de données) qui sont publiées à nouveau sur le client.

Lectures complémentaires:

Guide de cartographie avec bulldozer

Dozer est un mappeur Java Bean à Java Bean qui copie les données d'un objet à un autre, attribut par attribut, prend en charge le mappage entre les noms d'attribut, effectue la conversion de type et bien d'autres choses.

Read more

Guide rapide de MapStruct

Un guide rapide et pratique pour utiliser MapStruct

Read more

2. Model Mapper

Commençons par présenter la bibliothèque principale que nous allons utiliser pour effectuer cette conversion entité-DTO -ModelMapper.

Nous aurons besoin de cette dépendance dans lespom.xml:


    org.modelmapper
    modelmapper
    2.3.2

Pour vérifier s'il existe une version plus récente de cette bibliothèque,go here.

Nous définirons ensuite le beanModelMapper dans notre configuration Spring:

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

3. Le DTO

Ensuite, introduisons le côté DTO de ce problème à deux faces -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
}

Notez que les deux méthodes personnalisées liées à la date gèrent la conversion de date entre le client et le serveur:

  • La méthodegetSubmissionDateConverted() convertit la chaîne de date en une date dans le fuseau horaire du serveur pour l'utiliser dans l'entité de publication persistante

  • La méthodesetSubmissionDate() consiste à définir la date du DTO sur la date de publication dans le fuseau horaire de l'utilisateur actuel.

4. La couche de service

Examinons maintenant une opération de niveau de service - qui fonctionnera évidemment avec l'entité (pas le 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();
}

Nous allons ensuite examiner la couche au-dessus du service - la couche contrôleur. C'est là que la conversion se produira réellement.

5. La couche contrôleur

Voyons maintenant une implémentation de contrôleur standard, exposant l'API REST simple pour la ressourcePost.

Nous allons montrer ici quelques opérations CRUD simples: créer, mettre à jour, en obtenir une et tout obtenir. Et étant donné que les opérations sont assez simples,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);
    }
}

Et voiciour 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;
}

Et voici la conversionfrom 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;
}

Ainsi, comme vous pouvez le voir, avec l'aide du mappeur de modèle,the conversion logic is quick and simple - nous utilisons l'APImap du mappeur et convertissons les données sans écrire une seule ligne de logique de conversion.

6. Tests unitaires

Enfin, faisons un test très simple pour vous assurer que les conversions entre l’entité et le DTO fonctionnent correctement:

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. Conclusion

C'était un article sursimplifying the conversion from Entity to DTO and from DTO to Entity in a Spring REST API, en utilisant la bibliothèque du mappeur de modèles au lieu d'écrire ces conversions à la main.

Le code source complet des exemples est disponible dans lesGitHub project.