Projections et extraits dans Spring Data REST

Projections et extraits dans Spring Data REST

1. Vue d'ensemble

Dans cet article, nous allons explorer les concepts de projections et d'extraits de Spring Data REST.

Nous allons apprendre àuse projections to create custom views of our models and how to use excerpts as default views to resource collections.

2. Nos modèles de domaine

Commençons par définir nos modèles de domaine:Book etAuthor.

Jetons un coup d'œil à la classe d'entitéBook:

@Entity
public class Book {

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

    @Column(nullable = false)
    private String title;

    private String isbn;

    @ManyToMany(mappedBy = "books", fetch = FetchType.EAGER)
    private List authors;
}

Et le modèleAuthor:

@Entity
public class Author {

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

    @Column(nullable = false)
    private String name;

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(
      name = "book_author",
      joinColumns = @JoinColumn(
        name = "book_id", referencedColumnName = "id"),
      inverseJoinColumns = @JoinColumn(
        name = "author_id", referencedColumnName = "id"))
    private List books;
}

Les deux entités entretiennent également une relation plusieurs à plusieurs.

Ensuite, définissons les référentiels REST Spring Data standard pour chacun des modèles:

public interface BookRepository extends CrudRepository {}
public interface AuthorRepository extends CrudRepository {}

Maintenant, nous pouvons accéder au point de terminaisonBook pour obtenir des détails spécifiques deBook’s en utilisant son id àhttp://localhost:8080/books/{id}:

{
  "title" : "Animal Farm",
  "isbn" : "978-1943138425",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/books/1"
    },
    "book" : {
      "href" : "http://localhost:8080/books/1"
    },
    "authors" : {
      "href" : "http://localhost:8080/books/1/authors"
    }
  }
}

Notez que puisque le modèleAuthor a son référentiel, les détails des auteurs ne font pas partie de la réponse. On peut cependant trouver le lien vers eux -http://localhost:8080/books/1/authors.

3. Créer une projection

Parfois,we’re only interested in a subset or a custom view of an entity’s attributes. Dans de tels cas, nous pouvons utiliser des projections.

Créons une vue personnalisée de nosBook à l'aide des projections Spring Data REST.

Nous allons commencer par créer un simpleProjection appeléCustomBook:

@Projection(
  name = "customBook",
  types = { Book.class })
public interface CustomBook {
    String getTitle();
}

Notez queour projection is defined as an interface with an @Projection annotation. Nous pouvons utiliser l'attributname pour personnaliser le nom de la projection, ainsi que les attributstypes pour définir les objets auxquels elle s'applique.

Dans notre exemple, la projectionCustomBook n'inclura que lestitle d'un livre.

Regardons à nouveau notre représentationBook après avoir créé nosProjection:

{
  "title" : "Animal Farm",
  "isbn" : "978-1943138425",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/books/1"
    },
    "book" : {
      "href" : "http://localhost:8080/books/1{?projection}",
      "templated" : true
    },
    "authors" : {
      "href" : "http://localhost:8080/books/1/authors"
    }
  }
}

Génial, nous pouvons voir un lien vers notre projection. Vérifions la vue que nous avons créée àhttp://localhost:8080/books/1?projection=customBook:

{
  "title" : "Animal Farm",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/books/1"
    },
    "book" : {
      "href" : "http://localhost:8080/books/1{?projection}",
      "templated" : true
    },
    "authors" : {
      "href" : "http://localhost:8080/books/1/authors"
    }
  }
}

Ici, nous pouvons voir que nous n'obtenons que le champtitle, tandis que leisbn n'est plus présent dans la vue personnalisée.

En règle générale, nous pouvons accéder au résultat d’une projection àhttp://localhost:8080/books/1?projection=\{projection name}.

Notez également que nous devons définir nosProjection dans le même package que nos modèles. Alternativement, nous pouvons utiliserRepositoryRestConfigurerAdapter pour l'ajouter explicitement:

@Configuration
public class RestConfig extends RepositoryRestConfigurerAdapter {

    @Override
    public void configureRepositoryRestConfiguration(
      RepositoryRestConfiguration repositoryRestConfiguration) {
        repositoryRestConfiguration.getProjectionConfiguration()
          .addProjection(CustomBook.class);
    }
}

4. Ajout de nouvelles données aux projections

Voyons maintenant comment ajouter de nouvelles données à notre projection.

Comme nous en avons discuté dans la section précédente, nous pouvons utiliser une projection pour sélectionner les attributs à inclure dans notre vue. De plus, nous pouvons également ajouter des données qui ne sont pas incluses dans la vue d'origine.

4.1. Données cachées

Par défaut, les identifiants ne sont pas inclus dans la vue des ressources d'origine.

Pour voir les identifiants dans le résultat, nous pouvons inclure explicitement le champid:

@Projection(
  name = "customBook",
  types = { Book.class })
public interface CustomBook {
    @Value("#{target.id}")
    long getId();

    String getTitle();
}

Maintenant, la sortie àhttp://localhost:8080/books/1?projection=\{projection name} era:

{
  "id" : 1,
  "title" : "Animal Farm",
  "_links" : {
     ...
  }
}

Notez que nous pouvons également inclure des données qui ont été masquées de la vue d'origine avec@JsonIgnore.

4.2. Données calculées

Nous pouvons également inclure de nouvelles données calculées à partir de nos attributs de ressources.

Par exemple, nous pouvons inclure le nombre d’auteurs dans notre projection:

@Projection(name = "customBook", types = { Book.class })
public interface CustomBook {

    @Value("#{target.id}")
    long getId();

    String getTitle();

    @Value("#{target.getAuthors().size()}")
    int getAuthorCount();
}

Et nous pouvons le vérifier àhttp://localhost:8080/books/1?projection=customBook:

{
  "id" : 1,
  "title" : "Animal Farm",
  "authorCount" : 1,
  "_links" : {
     ...
  }
}

Enfin, si nous avons généralement besoin d'accéder à des ressources associées - comme dans notre exemple les auteurs d'un livre, nous pouvons éviter la demande supplémentaire en l'incluant explicitement:

@Projection(
  name = "customBook",
  types = { Book.class })
public interface CustomBook {

    @Value("#{target.id}")
    long getId();

    String getTitle();

    List getAuthors();

    @Value("#{target.getAuthors().size()}")
    int getAuthorCount();
}

Et la sortie finale deProjection sera:

{
  "id" : 1,
  "title" : "Animal Farm",
  "authors" : [ {
    "name" : "George Orwell"
  } ],
  "authorCount" : 1,
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/books/1"
    },
    "book" : {
      "href" : "http://localhost:8080/books/1{?projection}",
      "templated" : true
    },
    "authors" : {
      "href" : "http://localhost:8080/books/1/authors"
    }
  }
}

Ensuite, nous examinerons des extraits.

5. Extraits

Les extraits sont des projections que nous appliquons comme vues par défaut aux collections de ressources.

Personnalisons nosBookRepository pour utiliser automatiquement lescustomBookProjection pour la réponse de collecte.

Pour ce faire, nous utiliserons l'attributexcerptProjection de l'annotation@RepositoryRestResource:

@RepositoryRestResource(excerptProjection = CustomBook.class)
public interface BookRepository extends CrudRepository {}

Nous pouvons maintenant nous assurer quecustomBook est la vue par défaut de la collection de livres en appelanthttp://localhost:8080/books:

{
  "_embedded" : {
    "books" : [ {
      "id" : 1,
      "title" : "Animal Farm",
      "authors" : [ {
        "name" : "George Orwell"
      } ],
      "authorCount" : 1,
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/books/1"
        },
        "book" : {
          "href" : "http://localhost:8080/books/1{?projection}",
          "templated" : true
        },
        "authors" : {
          "href" : "http://localhost:8080/books/1/authors"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/books"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/books"
    }
  }
}

Il en va de même pour la visualisation de livres d'un auteur spécifique àhttp://localhost:8080/authors/1/books:

{
  "_embedded" : {
    "books" : [ {
      "id" : 1,
      "authors" : [ {
        "name" : "George Orwell"
      } ],
      "authorCount" : 1,
      "title" : "Animal Farm",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/books/1"
        },
        "book" : {
          "href" : "http://localhost:8080/books/1{?projection}",
          "templated" : true
        },
        "authors" : {
          "href" : "http://localhost:8080/books/1/authors"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/authors/1/books"
    }
  }
}

Comme mentionné, les extraits s’appliquent automatiquement uniquement aux ressources de la collection. Pour une seule ressource, nous devons utiliser le paramètreprojection comme indiqué dans les sections précédentes.

En effet, si nous appliquons les projections comme vue par défaut pour des ressources uniques, il sera difficile de savoir comment mettre à jour la ressource à partir d'une vue partielle.

Pour terminer, il est important de se rappeler queprojections and excerpts are meant for the read-only purpose.

6. Conclusion

Nous avons appris à utiliser les projections Spring Data REST pour créer des vues personnalisées de nos modèles. Nous avons également appris à utiliser des extraits comme vues par défaut pour les collections de ressources.

Le code source complet des exemples peut être trouvéover on GitHub.