Проекции и отрывки в Spring Data REST

Проекции и отрывки в Spring Data REST

1. обзор

В этой статье мы рассмотрим концепции прогнозов и отрывков Spring Data REST.

Мы узнаем, какuse projections to create custom views of our models and how to use excerpts as default views to resource collections.

2. Наши доменные модели

Во-первых, давайте начнем с определения наших моделей предметной области:Book иAuthor.

Давайте посмотрим на класс сущности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;
}

И модельAuthor:

@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;
}

Эти две сущности также имеют отношения многие ко многим.

Затем давайте определим стандартные репозитории Spring Data REST для каждой из моделей:

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

Теперь мы можем получить доступ к конечной точкеBook, чтобы получить конкретную информацию оBook’s, используя ее идентификатор в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. Создание проекции

Иногдаwe’re only interested in a subset or a custom view of an entity’s attributes. Для таких случаев мы можем использовать прогнозы.

Давайте создадим индивидуальное представление для нашихBook с помощью проекций Spring Data REST.

Начнем с создания простогоProjection под названиемCustomBook:

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

Обратите внимание, чтоour projection is defined as an interface with an @Projection annotation. Мы можем использовать атрибутname для настройки имени проекции, а также атрибутыtypes для определения объектов, к которым он применяется.

В нашем примере проекцияCustomBook будет включать толькоtitle книги.

Давайте еще раз посмотрим на наше представлениеBook после создания нашегоProjection:

{
  "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"
    }
  }
}

Здесь мы видим, что получаем только полеtitle, аisbn больше не присутствует в пользовательском представлении.

Как правило, мы можем получить доступ к результату прогноза вhttp://localhost:8080/books/1?projection=\{projection name}.

Также обратите внимание, что нам нужно определить нашProjection в том же пакете, что и наши модели. В качестве альтернативы мы можем использоватьRepositoryRestConfigurerAdapter, чтобы добавить его явно:

@Configuration
public class RestConfig extends RepositoryRestConfigurerAdapter {

    @Override
    public void configureRepositoryRestConfiguration(
      RepositoryRestConfiguration repositoryRestConfiguration) {
        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=\{projection name} будет:

{
  "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" : {
     ...
  }
}

Наконец, если нам обычно требуется доступ к связанным ресурсам - например, в нашем примере к авторам книги, мы можем избежать дополнительного запроса, включив его явно:

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

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

    String getTitle();

    List 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 на автоматическое использованиеcustomBookProjection для ответа на сборку.

Для этого мы будем использовать атрибутexcerptProjection аннотации@RepositoryRestResource:

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

Теперь мы можем убедиться, что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"
    }
  }
}

Как уже упоминалось, выдержки применяются автоматически только к ресурсам коллекции. Для одного ресурса мы должны использовать параметрprojection, как показано в предыдущих разделах.

Это связано с тем, что если мы применим проекции в качестве представления по умолчанию для отдельных ресурсов, то будет сложно узнать, как обновить ресурс из частичного представления.

В заключение, важно помнить, чтоprojections and excerpts are meant for the read-only purpose.

6. Заключение

Мы узнали, как использовать проекции Spring Data REST для создания пользовательских представлений наших моделей. Мы также узнали, как использовать выдержки в качестве представлений по умолчанию для коллекций ресурсов.

Полный исходный код примеров можно найти вover on GitHub.