投影與摘錄
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
是另一個網域物件的連結。
現在假設我們建立一個對應的 repository,如下所示
interface PersonRepository extends CrudRepository<Person, Long> {}
預設情況下,Spring Data REST 會匯出此網域物件,包括其所有屬性。firstName
和 lastName
會匯出為它們所是的純資料物件。關於 address
屬性,有兩個選項。一個選項是也為 Address
物件定義一個 repository,如下所示
interface AddressRepository extends CrudRepository<Address, Long> {}
在這種情況下,Person
資源會將 address
屬性呈現為其對應 Address
資源的 URI。如果我們在系統中查詢 “Frodo”,我們可以預期看到類似以下的 HAL 文件
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "https://127.0.0.1:8080/persons/1"
},
"address" : {
"href" : "https://127.0.0.1:8080/persons/1/address"
}
}
}
還有另一種方法。如果 Address
網域物件沒有自己的 repository 定義,Spring Data REST 會將資料欄位包含在 Person
資源內,如下列範例所示
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : {
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "https://127.0.0.1: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
資源設定了個別的 repository,Spring Data REST 的預設檢視與先前的表示法略有不同,如下列範例所示
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "https://127.0.0.1:8080/persons/1{?projection}", (1)
"templated" : true (2)
},
"address" : {
"href" : "https://127.0.0.1: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://127.0.0.1:8080/persons/1"
},
"address" : { (2)
"href" : "https://127.0.0.1:8080/persons/1/address"
}
}
}
1 | address 資料直接內嵌包含,因此您不必導航即可取得它。 |
2 | Address 資源的連結仍然提供,使其仍然可以導航到其自己的資源。 |
請注意,前面的範例是本章稍早顯示的範例的混合。您可能想要回頭閱讀它們,以追蹤到最終範例的進展。
為 repository 設定 @RepositoryRestResource(excerptProjection=…) 會變更預設行為。如果您已經發布版本,這可能會對您的服務的消費者造成重大變更。 |