Mise en place d'une API Rest avec Spring (2)

Traitement des relations

Préparer des données

Nous allons nous baser sur les entités ci-dessous :

package myboot.app3.model;

import java.util.List;

import jakarta.persistence.*;

@Entity
@lombok.Data
@lombok.NoArgsConstructor
public class Writer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Basic
    private String name;

    @Basic
    private String email;

    @Basic(fetch = FetchType.LAZY)
    private String description;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "writer")
    @lombok.ToString.Exclude
    private List<Post> posts;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "writer")
    @OrderColumn(name = "position")
    @lombok.ToString.Exclude
    private List<Comment> comments;

    public Writer(String name, String email, String description) {
        this.name = name;
        this.email = email;
        this.description = description;
    }

}
package myboot.app3.model;

import java.util.List;

import jakarta.persistence.*;

@Entity
@lombok.Data
@lombok.NoArgsConstructor
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Basic
    private String subject;

    @OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
    @lombok.ToString.Exclude
    private List<Comment> comments;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    @lombok.ToString.Exclude
    private Writer writer;

    public Post(String subject, Writer writer) {
        super();
        this.subject = subject;
        this.writer = writer;
    }

}
package myboot.app3.model;

import jakarta.persistence.*;

@Entity
@lombok.Data
@lombok.NoArgsConstructor
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Basic
    private String reply;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    @lombok.ToString.Exclude
    private Post post;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    @lombok.ToString.Exclude
    private Writer writer;

    public Comment(String reply, Post post, Writer writer) {
        super();
        this.reply = reply;
        this.post = post;
        this.writer = writer;
    }

}

Nous allons également définir les dépôts nécessaires :

package myboot.app3.dao;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import myboot.app3.model.Writer;

@Repository
@Transactional
public interface WriterRepository extends CrudRepository<Writer, Long> {
        
}
package myboot.app3.dao;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import myboot.app3.model.Post;

@Repository
@Transactional
public interface PostRepository extends CrudRepository<Post, Long> {

}
package myboot.app3.dao;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import myboot.app3.model.Comment;

@Repository
@Transactional
public interface CommentRepository extends CrudRepository<Comment, Long> {
        
}

Traiter les relations

Faites en sorte de créer des données qui comportent des boucles (Writer1 -> Post1 -> Comment1 -> Writer1) et d'autres pas (Writer2 -> Post2 -> Comment2 -> Writer3).

  • Commencez par prévoir un contrôleur pour traiter la route GET /writers/ID.
  • Quel résultat obtenez-vous ? Pouvez-vous récupérer les publications de tous les utilisateurs ?
  • Utilisez les annotations @JsonIgnore pour régler le problème des boucles.
  • Recommencez en utilisant @JsonBackReference (sur les liaisons inverses) et @JsonManagedReference (sur les collections) pour bien traiter les relations. Vérifiez par un test unitaire que la récupération d'un utilisateur, entraine aussi les publications et les commentaires.
  • Les boucles peuvent être gérées par l'ajout d'une annotation qui précise l'identifiant d'une instance. Testez la sérialisation/de-sérialisation de la classe ci-dessous (avec L1->L2->L1). Inspirez vous de ces exemples sans forcément mettre en place un service REST.
package myboot.app3.model;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import lombok.Data;
import lombok.NoArgsConstructor;

@JsonIdentityInfo(//
        generator = ObjectIdGenerators.PropertyGenerator.class, //
        property = "name"//
)
@Data
@NoArgsConstructor
public class Loop {

    private String name = "";

    private Loop loop;

    public Loop(String name) {
        super();
        this.name = name;
    }

}

Documenter votre API avec Swagger

Travail à faire :
  • Suivre cette documentation afin de comprendre la mise en place d'une documentation automatique de votre API. Attention à bien respecter la matrice de compatibilité en utilisant la version 3.1.1.
  • Utiliser l'annotation @Parameter présentée dans la documentation précédente pour donner des informations sur les paramètres ou sur le corps des requêtes.

Utiliser ModelMapper

Travail à faire :
  • Consultez ce site, ajoutez la dépendance et vérifiez l'exemple dans un test unitaire.
  • Faites en sorte de définir une objet MovieDTO dans lequel vous allez placer des directives de validation différente (sur l'année par exemple). Vous pouvez maintenant réclamer en entrée des requêtes POST /movies la version DTO et ainsi mieux contrôler les contraintes qui portent sur les instances créées.
  • Ajoutez à votre DTO la méthode ci-dessous et faites en sorte de renvoyer des instances de MovieDTO pour la requête GET /movies. Vous venez, facilement, de changer la forme des données renvoyées.
    public String getCompleteName() {
        return getName() + " " + getYear();
    }
    

Une courte introduction à Spring Rest Data

Pour la mise en oeuvre de Spring Rest Data n'oubliez pas de créer un package myboot.app4.dao pour les entités et les dépôts.

Travail à faire :
  • Prenez soin d'ajouter le paramètre spring.data.rest.basePath=/apibis dans le fichier application.properties afin de na pas avoir de confusion entre l'API générée et les anciennes faites à la main.