Projections and Excerpts
Spring Data REST 提供了你导出的领域模型的默认视图。不过,有时,你可能需要出于各种原因更改该模型的视图。本部分介绍如何定义投影和摘要,以提供资源的简化和精简视图。
Spring Data REST presents a default view of the domain model you export. However, sometimes, you may need to alter the view of that model for various reasons. This section covers how to define projections and excerpts to serve up simplified and reduced views of resources.
Projections
考虑以下领域模型:
Consider the following domain model:
@Entity
public class Person {
@Id @GeneratedValue
private Long id;
private String firstName, lastName;
@OneToOne
private Address address;
…
}
前一个示例中的 Person
对象具有多个属性:
The Person
object in the preceding example has several attributes:
-
id
is the primary key. -
firstName
andlastName
are data attributes. -
address
is a link to another domain object.
现在假设我们创建一个相应的存储库,如下所示:
Now assume that we create a corresponding repository, as follows:
interface PersonRepository extends CrudRepository<Person, Long> {}
默认情况下,Spring Data REST 导出此域对象,包括其所有属性。firstName
和 lastName
被导出为它们所在的纯数据对象。关于 address
属性有两个选项。一种选择是也为 Address
对象定义一个存储库,如下所示:
By default, Spring Data REST exports this domain object, including all of its attributes. firstName
and lastName
are exported as the plain data objects that they are. There are two options regarding the address
attribute. One option is to also define a repository for Address
objects, as follows:
interface AddressRepository extends CrudRepository<Address, Long> {}
在这种情况下,Person
资源将 address
属性呈现为指向其相应的 Address
资源的 URI。如果我们在系统中查找“Frodo
”,我们可能会看到这样的 HAL 文档:
In this situation, a Person
resource renders the address
attribute as a URI to its corresponding Address
resource. If we were to look up “Frodo” in the system, we could expect to see a HAL document like this:
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
},
"address" : {
"href" : "http://localhost:8080/persons/1/address"
}
}
}
还有另一种方法。如果 Address
域对象没有自己的存储库定义,则 Spring Data REST 会将数据字段包含在 Person
资源中,如下例所示:
There is another way. If the Address
domain object does not have its own repository definition, Spring Data REST includes the data fields inside the Person
resource, as the following example shows:
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : {
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
}
}
}
但是,如果你根本不需要 address
详细信息呢?同样,默认情况下,Spring Data REST 会导出其所有属性(id
除外)。你可以通过定义一个或多个投影为你的 REST 服务的使用者提供替代方案。以下示例显示了一个不包含地址的投影:
But what if you do not want address
details at all? Again, by default, Spring Data REST exports all of its attributes (except the id
). You can offer the consumer of your REST service an alternative by defining one or more projections. The following example shows a projection that does not include the address:
@Projection(name = "noAddresses", types = { Person.class }) 1
interface NoAddresses { 2
String getFirstName(); 3
String getLastName(); 4
}
1 | The @Projection annotation flags this as a projection. The name attribute provides
the name of the projection, which we cover in more detail shortly. The types attributes targets this projection to apply only to Person objects. |
2 | It is a Java interface, making it declarative. |
3 | It exports the firstName . |
4 | It exports the lastName . |
NoAddresses
投影仅有 firstName
和 lastName
的 getter,这意味着它不会提供任何地址信息。假设你为 Address
资源有一个单独的存储库,则 Spring Data REST 的默认视图与以前的表示略有不同,如下例所示:
The NoAddresses
projection only has getters for firstName
and lastName
, meaning that it does not serve up any address information. Assuming you have a separate repository for Address
resources, the default view from Spring Data REST differs slightly from the previous representation, as the following example shows:
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1{?projection}", 1
"templated" : true 2
},
"address" : {
"href" : "http://localhost:8080/persons/1/address"
}
}
}
1 | This resource has a new option: {?projection} . |
2 | The self URI is a URI Template. |
若要查看对资源的投影,请查找 http://localhost:8080/persons/1?projection=noAddresses
。
To view the projection to the resource, look up http://localhost:8080/persons/1?projection=noAddresses
.
提供给 |
The value supplied to the |
您可以有多个投影。
You can have multiple projections.
请参阅“ Projections”以查看一个示例项目。我们鼓励您尝试一下。 |
See Projections to see an example project. We encourage you to experiment with it. |
Spring Data REST 以如下方式查找投影定义:
Spring Data REST finds projection definitions as follows:
-
Any
@Projection
interface found in the same package as your entity definitions (or one of its sub-packages) is registered. -
You can manually register a projection by using
RepositoryRestConfiguration.getProjectionConfiguration().addProjection(…)
.
无论哪种情况,投影接口都必须具有 @Projection
注解。
In either case, the projection interface must have the @Projection
annotation.
Finding Existing Projections
Spring Data REST 公开了 Application-Level Profile Semantics (ALPS) 文档,即微型元数据格式。要查看 ALPS 元数据,请访问根资源公开的 profile
链接。如果向下导航到 Person
资源(为 /alps/persons
)的 ALPS 文档,您将找到关于 Person
资源的大量详细信息。投影列在以下示例的块内,以及关于 GET
REST 转移的详细信息:
Spring Data REST exposes Application-Level Profile Semantics (ALPS) documents, a micro metadata format. To view the ALPS metadata, follow the profile
link exposed by the root resource. If you navigate down to the ALPS document for Person
resources (which would be /alps/persons
), you can find many details about Person
resources. Projections are listed, along with the details about the GET
REST transition, in blocks similar to the following example:
{ …
"id" : "get-person", 1
"name" : "person",
"type" : "SAFE",
"rt" : "#person-representation",
"descriptors" : [ {
"name" : "projection", 2
"doc" : {
"value" : "The projection that shall be applied when rendering the response. Acceptable values available in nested descriptors.",
"format" : "TEXT"
},
"type" : "SEMANTIC",
"descriptors" : [ {
"name" : "noAddresses", 3
"type" : "SEMANTIC",
"descriptors" : [ {
"name" : "firstName", 4
"type" : "SEMANTIC"
}, {
"name" : "lastName", 4
"type" : "SEMANTIC"
} ]
} ]
} ]
},
…
1 | This part of the ALPS document shows details about GET and Person resources. |
2 | This part contais the projection options. |
3 | This part contains the noAddresses projection. |
4 | The actual attributes served up by this projection include firstName and lastName . |
如果满足以下条件,投影定义将被选中并提供给客户: Projection definitions are picked up and made available for clients if they are:
|
Bringing in Hidden Data
本部分到目前为止,我们已经介绍了如何使用投影来减少呈现给用户的信息。投影也可以引入通常不可见的数据。例如,Spring Data REST 忽略使用 @JsonIgnore
注解标记的字段或 getter。请考虑以下域对象:
So far in this section, we have covered how projections can be used to reduce the information that is presented to the user. Projections can also bring in normally unseen data. For example, Spring Data REST ignores fields or getters that are marked up with @JsonIgnore
annotations. Consider the following domain object:
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@JsonIgnore private String password; 1
private String[] roles;
…
1 | Jackson’s @JsonIgnore is used to prevent the password field from being serialized into JSON. |
前一个示例中的 User
类可用于存储用户信息以及与 Spring Security 集成。如果您创建一个 UserRepository
,通常会导出 password
字段,这并不好。在前一个示例中,通过在 password
字段上应用 Jackson 的 @JsonIgnore
来阻止这种情况发生。
The User
class in the preceding example can be used to store user information as well as integration with Spring Security. If you create a UserRepository
, the password
field would normally have been exported, which is not good. In the preceding example, we prevent that from happening by applying Jackson’s @JsonIgnore
on the password
field.
如果`@JsonIgnore`在字段的相应 getter 函数上,Jackson 也不会将该字段序列化为 JSON。 |
Jackson also does not serialize the field into JSON if |
但是,投影引入了仍然提供此字段的功能。可以创建以下投影:
However, projections introduce the ability to still serve this field. It is possible to create the following projection:
@Projection(name = "passwords", types = { User.class })
interface PasswordProjection {
String getPassword();
}
如果创建并使用此类投影,它将绕过对 User.password
放置的 @JsonIgnore
指令。
If such a projection is created and used, it sidesteps the @JsonIgnore
directive placed on User.password
.
此示例看起来可能有点牵强,但如果使用更丰富的域模型和许多投影,则有可能意外泄露这类详细信息。由于 Spring Data REST 无法分辨此类数据的敏感性,因此避免此类情况取决于你。
This example may seem a bit contrived, but it is possible, with a richer domain model and many projections, to accidentally leak such details. Since Spring Data REST cannot discern the sensitivity of such data, it is up to you to avoid such situations.
投影还可以生成虚拟数据。想象一下您有以下实体定义:
Projections can also generate virtual data. Imagine you had the following entity definition:
@Entity
public class Person {
...
private String firstName;
private String lastName;
...
}
您可以创建以下投影,将前一个示例中的两个数据字段组合在一起:
You can create a projection that combines the two data fields in the preceding example together, as follows:
@Projection(name = "virtual", types = { Person.class })
public interface VirtualProjection {
@Value("#{target.firstName} #{target.lastName}") 1
String getFullName();
}
1 | Spring’s @Value annotation lets you plug in a SpEL expression that takes the target object and splices together its firstName and lastName attributes to render a read-only fullName . |
Excerpts
摘录是一种自动应用于资源集合的投影。例如,您可以如下更改 PersonRepository
:
An excerpt is a projection that is automatically applied to a resource collection. For example, you can alter the PersonRepository
as follows:
@RepositoryRestResource(excerptProjection = NoAddresses.class)
interface PersonRepository extends CrudRepository<Person, Long> {}
前一个示例指示 Spring Data REST 在将 Person
资源嵌入到集合或关联资源时使用 NoAddresses
投影。
The preceding example directs Spring Data REST to use the NoAddresses
projection when embedding Person
resources into collections or related resources.
摘录投影不会自动应用到单个资源上。它们必须被故意应用。摘录投影的目的是提供集合数据的一个默认预览,而不是在获取单个资源时提供。有关此主题的讨论,请参阅“ Why is an excerpt projection not applied automatically for a Spring Data REST item resource?”。 |
Excerpt projections are not automatically applied to single resources. They have to be applied deliberately. Excerpt projections are meant to provide a default preview of collection data but not when fetching individual resources. See Why is an excerpt projection not applied automatically for a Spring Data REST item resource? for a discussion on the subject. |
除了更改默认呈现之外,摘要还有其他呈现选项,如下一部分所示。
In addition to altering the default rendering, excerpts have additional rendering options as shown in the next section.
Excerpting Commonly Accessed Data
当您组合域对象时,REST 服务经常会遇到一种常见情况。例如,将 Person
存储在一个表中,将相关 Address
存储在另一个表中。默认情况下,Spring Data REST 将用户的 address
作为一个 URI 提供给客户端,客户端必须进行导航。但是,如果使用者经常会获取这一额外的部分数据,则摘录投影可以将这一额外的部分数据内联,这样您就可以少执行一个 GET
。要这样做,您可以定义另一个摘录投影,如下所示:
A common situation with REST services arises when you compose domain objects. For example, a Person
is stored in one table and their related Address
is stored in another. By default, Spring Data REST serves up the person’s address
as a URI the client must navigate. But if it is common for consumers to always fetch this extra piece of data, an excerpt projection can put this extra piece of data inline, saving you an extra GET
. To do so, you can define another excerpt projection, as follows:
@Projection(name = "inlineAddress", types = { Person.class }) 1
interface InlineAddress {
String getFirstName();
String getLastName();
Address getAddress(); 2
}
1 | This projection has been named inlineAddress . |
2 | This projection adds getAddress , which returns the Address field. When used inside a projection, it causes the information to be included inline. |
您可以将其插入 PersonRepository
定义,如下所示:
You can plug it into the PersonRepository
definition, as follows:
@RepositoryRestResource(excerptProjection = InlineAddress.class)
interface PersonRepository extends CrudRepository<Person, Long> {}
这样做将使 HAL 文档显示如下:
Doing so causes the HAL document to appear as follows:
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : { 1
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
},
"address" : { 2
"href" : "http://localhost:8080/persons/1/address"
}
}
}
1 | The address data is directly included inline, so you do not have to navigate to get it. |
2 | The link to the Address resource is still provided, making it still possible to navigate to its own resource. |
注意,前一个示例混合了本章前面章节中所示的示例。您可能需要回头阅读它们,了解最终示例的进展。
Note that the preceding example is a mix of the examples shown earlier in this chapter. You may want to read back through them to follow the progression to the final example.
为存储库配置`@RepositoryRestResource(excerptProjection=…)`会更改默认行为。如果你已经发布了服务,这有可能会对你的服务使用者导致重大更改。
Configuring @RepositoryRestResource(excerptProjection=…)
for a repository alters the default behavior. This can potentially cause breaking changes to consumers of your service if you have already made a release.