Перейти к основному содержимому

Прогнозы и выдержки в Spring Data REST

· 7 мин. чтения

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 .