1. Обзор
В этой статье мы рассмотрим концепции прогнозов и выдержек Spring Data REST.
Мы узнаем, как использовать проекции для создания пользовательских представлений наших моделей и как использовать выдержки в качестве представлений по умолчанию для коллекций ресурсов .
2. Наши модели доменов
Во-первых, давайте начнем с определения наших моделей предметной области: Книга
и Автор.
Давайте посмотрим на класс сущности 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<Author> authors;
}
И модель автора
:
@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<Book> books;
}
Эти две сущности также имеют отношения «многие ко многим».
Далее давайте определим стандартные репозитории Spring Data REST для каждой из моделей:
public interface BookRepository extends CrudRepository<Book, Long> {}
public interface AuthorRepository extends CrudRepository<Author, Long> {}
Теперь мы можем получить доступ к конечной точке книги
, чтобы получить сведения о конкретной книге,
используя ее идентификатор по адресу 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"
}
}
}
Обратите внимание, что, поскольку у модели Author
есть репозиторий, сведения об авторах не являются частью ответа. Однако мы можем найти ссылку на них — http://localhost:8080/books/1/authors.
3. Создание проекции
Иногда нас интересует только подмножество или настраиваемое представление атрибутов объекта . Для таких случаев мы можем использовать проекции.
Давайте создадим собственное представление для нашей книги
, используя проекции Spring Data REST.
Мы начнем с создания простой проекции
под названием CustomBook
:
@Projection(
name = "customBook",
types = { Book.class })
public interface CustomBook {
String getTitle();
}
Обратите внимание, что наша проекция определяется как интерфейс с аннотацией @Projection
. Мы можем использовать атрибут имени
для настройки имени проекции, а также атрибуты типов
для определения объектов, к которым он применяется.
В нашем примере проекция CustomBook
будет включать только название
книги.
Давайте снова посмотрим на наше представление книги
после создания нашей проекции:
{
"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"
}
}
}
Отлично, мы видим ссылку на нашу проекцию. Давайте проверим представление, которое мы создали по адресу 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"
}
}
}
Здесь мы видим, что мы получаем только поле заголовка
, в то время как isbn
больше не присутствует в пользовательском представлении.
Как правило, мы можем получить доступ к результату проекции по адресу http://localhost:8080/books/1?projection={имя проекции}.
Также обратите внимание, что нам нужно определить нашу проекцию
в том же пакете, что и наши модели. В качестве альтернативы мы можем использовать RepositoryRestConfigurerAdapter
, чтобы добавить его явно:
@Configuration
public class RestConfig implements RepositoryRestConfigurer {
@Override
public void configureRepositoryRestConfiguration(
RepositoryRestConfiguration repositoryRestConfiguration, CorsRegistry cors) {
repositoryRestConfiguration.getProjectionConfiguration()
.addProjection(CustomBook.class);
}
}
4. Добавление новых данных в прогнозы
Теперь давайте посмотрим, как добавить новые данные в нашу проекцию.
Как мы обсуждали в предыдущем разделе, мы можем использовать проекцию, чтобы выбрать, какие атрибуты включить в наше представление. Более того, мы также можем добавить данные, которых нет в исходном представлении.
4.1. Скрытые данные
По умолчанию идентификаторы не включаются в исходное представление ресурсов.
Чтобы увидеть идентификаторы в результате, мы можем явно включить поле id :
@Projection(
name = "customBook",
types = { Book.class })
public interface CustomBook {
@Value("#{target.id}")
long getId();
String getTitle();
}
Теперь вывод по адресу http://localhost:8080/books/1?projection={имя проекции}
будет таким:
{
"id" : 1,
"title" : "Animal Farm",
"_links" : {
...
}
}
Обратите внимание, что мы также можем включить данные, которые были скрыты от исходного представления, с помощью @JsonIgnore.
4.2. Расчетные данные
Мы также можем включить новые данные, рассчитанные на основе атрибутов наших ресурсов.
Например, мы можем включить количество авторов в нашу проекцию:
@Projection(name = "customBook", types = { Book.class })
public interface CustomBook {
@Value("#{target.id}")
long getId();
String getTitle();
@Value("#{target.getAuthors().size()}")
int getAuthorCount();
}
И мы можем проверить это по адресу http://localhost:8080/books/1?projection=customBook
:
{
"id" : 1,
"title" : "Animal Farm",
"authorCount" : 1,
"_links" : {
...
}
}
4.3. Легкий доступ к связанным ресурсам
Наконец, если нам обычно требуется доступ к связанным ресурсам — например, в нашем примере к авторам книги, — мы можем избежать дополнительного запроса, включив его явно:
@Projection(
name = "customBook",
types = { Book.class })
public interface CustomBook {
@Value("#{target.id}")
long getId();
String getTitle();
List<Author> getAuthors();
@Value("#{target.getAuthors().size()}")
int getAuthorCount();
}
И окончательный вывод Projection
будет:
{
"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"
}
}
}
Далее мы рассмотрим отрывки.
5. Выдержки
Выдержки — это проекции, которые мы применяем в качестве представлений по умолчанию для коллекций ресурсов.
Давайте настроим наш BookRepository
для автоматического использования customBook
Projection
для ответа коллекции.
Для этого мы будем использовать атрибут excerptProjection
аннотации @RepositoryRestResource :
@RepositoryRestResource(excerptProjection = CustomBook.class)
public interface BookRepository extends CrudRepository<Book, Long> {}
Теперь мы можем убедиться, что customBook
является представлением по умолчанию для коллекции книг, вызвав http://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"
}
}
}
То же самое относится и к просмотру книг определенного автора по адресу 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"
}
}
}
Как уже упоминалось, выдержки применяются автоматически только к ресурсам коллекции. Для одного ресурса мы должны использовать параметр проекции
, как показано в предыдущих разделах.
Это связано с тем, что если мы применим проекции в качестве представления по умолчанию для отдельных ресурсов, будет сложно узнать, как обновить ресурс из частичного представления.
И последнее замечание: важно помнить, что прогнозы и выдержки предназначены только для чтения .
6. Заключение
Мы узнали, как использовать проекции Spring Data REST для создания пользовательских представлений наших моделей. Мы также узнали, как использовать выдержки в качестве представлений по умолчанию для коллекций ресурсов.
Полный исходный код примеров можно найти на GitHub .