Projections and Excerpts in Spring Data REST


1.概述

在本文中 - 我们将探索Spring Data REST的投影和摘录概念。

我们将学习如何使用投影来创建模型的自定义视图以及如何将摘录用作资源集合的默认视图。

2.Our Domain Models

首先,让我们从定义我们的领域模型开始: Book and 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<Author> authors;
}

And the Author model:

@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}中的id访问Book端点以获取特定Book的详细信息:

{
  "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投影为我们的Book创建一个自定义视图。

我们首先创建一个 名为 CustomBook的简单投影:

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

请注意,我们的投影被定义为带有@Projection注释的接口。我们可以使用name属性来自定义投影的名称,以及 用于定义它所应用的对象的types属性。

在我们的示例中,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"
    }
  }
}

在这里,我们可以看到我们只获取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.向投影添加新数据

现在,让我们看看如何在我们的投影中添加新数据。

正如我们在上一节中讨论的那样,我们可以使用Projection来选择要包含在视图中的属性。更重要的是,我们还可以添加原始视图中未包含的数据。

4.1。隐藏数据

默认情况下,ID不包含在原始资源视图中。

要查看结果中的ID,我们可以显式包含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" : {
     ...
  }
}

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();
}

最终的投影输出将是:

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

现在我们可以通过调用http:// localhost:8080 / books来确保 customBook是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"
    }
  }
}

如上所述,摘录仅自动应用于收集资源。对于单个资源,我们必须使用投影参数,如前面部分所示。

这是因为如果我们将Projections应用为单个资源的默认视图,则很难知道如何从局部视图更新资源。

最后要注意的是,重要的是要记住预测和摘录是出于只读的目的。