投影和摘錄
Spring Data REST 呈現您匯出的領域模型的預設檢視。然而,有時,您可能需要出於各種原因更改該模型的檢視。本節介紹如何定義投影和摘錄,以提供簡化和縮減的資源檢視。
投影
考慮以下領域模型
@Entity
public class Person {
@Id @GeneratedValue
private Long id;
private String firstName, lastName;
@OneToOne
private Address address;
…
}
前面示例中的 Person 物件有幾個屬性
-
id是主鍵。 -
firstName和lastName是資料屬性。 -
address是指向另一個領域物件的連結。
現在假設我們建立了一個相應的儲存庫,如下所示
interface PersonRepository extends CrudRepository<Person, Long> {}
預設情況下,Spring Data REST 匯出此領域物件,包括其所有屬性。firstName 和 lastName 作為其本身的普通資料物件匯出。address 屬性有兩種選擇。一種選擇是也為 Address 物件定義一個儲存庫,如下所示
interface AddressRepository extends CrudRepository<Address, Long> {}
在這種情況下,Person 資源將 address 屬性呈現為其相應 Address 資源的 URI。如果我們在系統中查詢“Frodo”,我們可能會看到如下的 HAL 文件
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "https://:8080/persons/1"
},
"address" : {
"href" : "https://:8080/persons/1/address"
}
}
}
還有另一種方式。如果 Address 領域物件沒有自己的儲存庫定義,Spring Data REST 將資料欄位包含在 Person 資源中,如以下示例所示
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : {
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "https://:8080/persons/1"
}
}
}
但是,如果您根本不想要 address 詳細資訊怎麼辦?同樣,預設情況下,Spring Data REST 匯出其所有屬性(id 除外)。您可以透過定義一個或多個投影來為 REST 服務的消費者提供替代方案。以下示例顯示了一個不包含地址的投影
@Projection(name = "noAddresses", types = { Person.class }) (1)
interface NoAddresses { (2)
String getFirstName(); (3)
String getLastName(); (4)
}
| 1 | @Projection 註解將其標記為投影。name 屬性提供了投影的名稱,我們稍後將更詳細地介紹。types 屬性將此投影的目標設定為僅適用於 Person 物件。 |
| 2 | 它是一個 Java 介面,使其具有宣告性。 |
| 3 | 它匯出 firstName。 |
| 4 | 它匯出 lastName。 |
NoAddresses 投影只包含 firstName 和 lastName 的 getter,這意味著它不提供任何地址資訊。假設您有一個單獨的 Address 資源儲存庫,Spring Data REST 的預設檢視與之前的表示略有不同,如以下示例所示
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "https://:8080/persons/1{?projection}", (1)
"templated" : true (2)
},
"address" : {
"href" : "https://:8080/persons/1/address"
}
}
}
| 1 | 此資源有一個新選項:{?projection}。 |
| 2 | self URI 是一個 URI 模板。 |
要檢視資源的投影,請查詢 localhost:8080/persons/1?projection=noAddresses。
提供給 projection 查詢引數的值與 @Projection(name = "noAddress") 中指定的值相同。它與投影介面的名稱無關。 |
您可以有多個投影。
| 請參閱 投影 以檢視示例專案。我們鼓勵您進行實驗。 |
Spring Data REST 按如下方式查詢投影定義
-
在您的實體定義所在包(或其子包之一)中找到的任何
@Projection介面都將註冊。 -
您可以透過使用
RepositoryRestConfiguration.getProjectionConfiguration().addProjection(…)手動註冊投影。
無論哪種情況,投影介面都必須帶有 @Projection 註解。
查詢現有投影
Spring Data REST 暴露了 應用程式級配置檔案語義 (ALPS) 文件,這是一種微元資料格式。要檢視 ALPS 元資料,請遵循根資源暴露的 profile 連結。如果您導航到 Person 資源的 ALPS 文件(即 /alps/persons),您可以找到關於 Person 資源的許多詳細資訊。投影與 GET REST 轉換的詳細資訊一起列在類似於以下示例的塊中
{ …
"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 | ALPS 文件的這一部分顯示了關於 GET 和 Person 資源的詳細資訊。 |
| 2 | 此部分包含 projection 選項。 |
| 3 | 此部分包含 noAddresses 投影。 |
| 4 | 此投影實際提供的屬性包括 firstName 和 lastName。 |
|
如果投影定義符合以下條件,則會被拾取並提供給客戶端:
|
引入隱藏資料
到目前為止,本節我們已經介紹瞭如何使用投影來減少呈現給使用者的資訊。投影還可以引入通常看不見的資料。例如,Spring Data REST 會忽略帶有 @JsonIgnore 註解的欄位或 getter。請考慮以下領域物件
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@JsonIgnore private String password; (1)
private String[] roles;
…
| 1 | Jackson 的 @JsonIgnore 用於防止 password 欄位序列化為 JSON。 |
前面示例中的 User 類可用於儲存使用者資訊以及與 Spring Security 的整合。如果您建立 UserRepository,password 欄位通常會被匯出,這不是一個好主意。在前面的示例中,我們透過在 password 欄位上應用 Jackson 的 @JsonIgnore 來防止這種情況發生。
如果 @JsonIgnore 在欄位對應的 getter 函式上,Jackson 也不會將該欄位序列化為 JSON。 |
然而,投影引入了仍然可以提供此欄位的能力。可以建立以下投影
@Projection(name = "passwords", types = { User.class })
interface PasswordProjection {
String getPassword();
}
如果建立並使用了這樣的投影,它將繞過應用於 User.password 的 @JsonIgnore 指令。
| 這個例子可能看起來有點牽強,但在更豐富的領域模型和許多投影的情況下,可能會意外地洩露這些細節。由於 Spring Data REST 無法辨別此類資料的敏感性,因此您需要避免此類情況。 |
投影還可以生成虛擬資料。假設您有以下實體定義
@Entity
public class Person {
...
private String firstName;
private String lastName;
...
}
您可以建立一個投影,將前面示例中的兩個資料欄位組合在一起,如下所示
@Projection(name = "virtual", types = { Person.class })
public interface VirtualProjection {
@Value("#{target.firstName} #{target.lastName}") (1)
String getFullName();
}
| 1 | Spring 的 @Value 註解讓您可以插入一個 SpEL 表示式,該表示式獲取目標物件並將其 firstName 和 lastName 屬性拼接在一起,以呈現一個只讀的 fullName。 |
摘錄
摘錄是一種自動應用於資源集合的投影。例如,您可以修改 PersonRepository 如下所示
@RepositoryRestResource(excerptProjection = NoAddresses.class)
interface PersonRepository extends CrudRepository<Person, Long> {}
前面的示例指示 Spring Data REST 在將 Person 資源嵌入到集合或相關資源中時使用 NoAddresses 投影。
| 摘錄投影不會自動應用於單個資源。它們必須有意地應用。摘錄投影旨在提供集合資料的預設預覽,而不是在獲取單個資源時使用。請參閱 為什麼 Spring Data REST 專案資源不自動應用摘錄投影? 瞭解關於該主題的討論。 |
除了更改預設渲染外,摘錄還有額外的渲染選項,如下一節所示。
摘錄常用資料
REST 服務中常見的情況是當您組合領域物件時。例如,Person 儲存在一個表中,而他們相關的 Address 儲存在另一個表中。預設情況下,Spring Data REST 將人員的 address 作為客戶端必須導航的 URI 提供。但是,如果消費者通常總是獲取這額外的資料,則摘錄投影可以將這額外的資料內聯,從而為您節省一次額外的 GET。為此,您可以定義另一個摘錄投影,如下所示
@Projection(name = "inlineAddress", types = { Person.class }) (1)
interface InlineAddress {
String getFirstName();
String getLastName();
Address getAddress(); (2)
}
| 1 | 此投影已命名為 inlineAddress。 |
| 2 | 此投影添加了 getAddress,它返回 Address 欄位。當在投影內部使用時,它會導致資訊以內聯方式包含。 |
您可以將其插入 PersonRepository 定義中,如下所示
@RepositoryRestResource(excerptProjection = InlineAddress.class)
interface PersonRepository extends CrudRepository<Person, Long> {}
這樣做會導致 HAL 文件顯示如下
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : { (1)
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "https://:8080/persons/1"
},
"address" : { (2)
"href" : "https://:8080/persons/1/address"
}
}
}
| 1 | address 資料直接以內聯方式包含,因此您不必導航即可獲取它。 |
| 2 | 仍然提供了指向 Address 資源的連結,使其仍然可以導航到其自己的資源。 |
請注意,前面的示例是本章前面所示示例的混合。您可能需要回溯閱讀它們,以瞭解最終示例的進展。
為儲存庫配置 @RepositoryRestResource(excerptProjection=…) 會改變預設行為。如果您已經發布,這可能會對您的服務的消費者造成重大更改。 |