Projeções e trechos no Spring Data REST
1. Visão geral
Neste artigo, vamos explorar os conceitos de projeções e trechos do Spring Data REST.
Aprenderemos comouse projections to create custom views of our models and how to use excerpts as default views to resource collections.
2. Nossos modelos de domínio
Primeiro, vamos começar definindo nossos modelos de domínio:BookeAuthor.
Vamos dar uma olhada na classe de entidadeBook:
@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;
}
E o modeloAuthor:
@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;
}
As duas entidades também têm um relacionamento muitos para muitos.
A seguir, vamos definir os repositórios REST Spring Data padrão para cada um dos modelos:
public interface BookRepository extends CrudRepository {}
public interface AuthorRepository extends CrudRepository {}
Agora, podemos acessar o endpointBook para obter detalhes específicos deBook’s usando seu id emhttp://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"
}
}
}
Observe que, como o modeloAuthor tem seu repositório, os detalhes do autor não fazem parte da resposta. Podemos, no entanto, encontrar o link para eles -http://localhost:8080/books/1/authors.
3. Criando uma Projeção
Às vezes,we’re only interested in a subset or a custom view of an entity’s attributes. Para tais casos, podemos fazer uso de projeções.
Vamos criar uma visualização personalizada para nossoBook usando as projeções REST do Spring Data.
Começaremos criando umProjection simples chamadoCustomBook:
@Projection(
name = "customBook",
types = { Book.class })
public interface CustomBook {
String getTitle();
}
Observe queour projection is defined as an interface with an @Projection annotation. Podemos usar o atributoname para personalizar o nome da projeção, bem como os atributostypes para definir os objetos aos quais se aplica.
Em nosso exemplo, a projeção deCustomBook incluirá apenas otitle de um livro.
Vamos dar uma olhada novamente em nossa representaçãoBook depois de criar nossoProjection:
{
"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"
}
}
}
Ótimo, podemos ver um link para nossa projeção. Vamos verificar a visualização que criamos emhttp://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"
}
}
}
Aqui, podemos ver que obtemos apenas o campotitle, enquanto oisbn não está mais presente na visualização personalizada.
Como regra geral, podemos acessar o resultado de uma projeção emhttp://localhost:8080/books/1?projection=\{projection name}.
Além disso, observe que precisamos definir nossoProjection no mesmo pacote que nossos modelos. Como alternativa, podemos usarRepositoryRestConfigurerAdapter para adicioná-lo explicitamente:
@Configuration
public class RestConfig extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(
RepositoryRestConfiguration repositoryRestConfiguration) {
repositoryRestConfiguration.getProjectionConfiguration()
.addProjection(CustomBook.class);
}
}
4. Adicionando Novos Dados às Projeções
Agora, vamos ver como adicionar novos dados à nossa projeção.
Como discutimos na seção anterior, podemos usar uma projeção para selecionar quais atributos incluir em nossa visão. Além do mais, também podemos adicionar dados que não estão incluídos na visualização original.
4.1. Dados ocultos
Por padrão, os IDs não são incluídos na exibição de recurso original.
Para ver os ids no resultado, podemos incluir o campoid explicitamente:
@Projection(
name = "customBook",
types = { Book.class })
public interface CustomBook {
@Value("#{target.id}")
long getId();
String getTitle();
}
Agora a saída emhttp://localhost:8080/books/1?projection=\{projection name} erá:
{
"id" : 1,
"title" : "Animal Farm",
"_links" : {
...
}
}
Observe que também podemos incluir dados que foram ocultados da visualização original com@JsonIgnore.
4.2. Dados Calculados
Também podemos incluir novos dados calculados a partir de nossos atributos de recursos.
Por exemplo, podemos incluir a contagem de autores em nossa projeção:
@Projection(name = "customBook", types = { Book.class })
public interface CustomBook {
@Value("#{target.id}")
long getId();
String getTitle();
@Value("#{target.getAuthors().size()}")
int getAuthorCount();
}
E podemos verificar emhttp://localhost:8080/books/1?projection=customBook:
{
"id" : 1,
"title" : "Animal Farm",
"authorCount" : 1,
"_links" : {
...
}
}
4.3. Acesso fácil a recursos relacionados
Finalmente, se normalmente precisamos acessar recursos relacionados - como em nosso exemplo os autores de um livro, podemos evitar a solicitação extra incluindo-o explicitamente:
@Projection(
name = "customBook",
types = { Book.class })
public interface CustomBook {
@Value("#{target.id}")
long getId();
String getTitle();
List getAuthors();
@Value("#{target.getAuthors().size()}")
int getAuthorCount();
}
E a saída final deProjection será:
{
"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"
}
}
}
A seguir, vamos dar uma olhada nos trechos.
5. Trechos
Trechos são projeções que aplicamos como visualizações padrão às coleções de recursos.
Vamos personalizar nossoBookRepository para usarcustomBookProjection automaticamente para a resposta da coleção.
Para conseguir isso, usaremos o atributoexcerptProjection da anotação@RepositoryRestResource:
@RepositoryRestResource(excerptProjection = CustomBook.class)
public interface BookRepository extends CrudRepository {}
Agora podemos ter certeza de quecustomBook é a visualização padrão da coleção de livros chamandohttp://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"
}
}
}
O mesmo se aplica à visualização de livros de um autor específico emhttp://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"
}
}
}
Como mencionado, trechos se aplicam automaticamente apenas aos recursos de coleta. Para um único recurso, devemos usar o parâmetroprojection conforme mostrado nas seções anteriores.
Isso ocorre porque, se aplicarmos as projeções como a visualização padrão para recursos únicos, será difícil saber como atualizar o recurso a partir de uma visualização parcial.
Como nota final, é importante lembrar queprojections and excerpts are meant for the read-only purpose.
6. Conclusão
Aprendemos como usar as projeções REST do Spring Data para criar visualizações personalizadas de nossos modelos. Também aprendemos como usar trechos como visualizações padrão para coleções de recursos.
O código-fonte completo dos exemplos pode ser encontradoover on GitHub.