Spring Data 擴展
本節記錄了一組 Spring Data 擴展,這些擴展使得 Spring Data 可以在各種情境中使用。目前,大多數整合都針對 Spring MVC。
Querydsl 擴展
Querydsl 是一個框架,它通過其流暢的 API 能夠構建靜態類型的類 SQL 查詢。
幾個 Spring Data 模組通過 QuerydslPredicateExecutor
提供了與 Querydsl 的整合,如下面的範例所示
public interface QuerydslPredicateExecutor<T> {
Optional<T> findById(Predicate predicate); (1)
Iterable<T> findAll(Predicate predicate); (2)
long count(Predicate predicate); (3)
boolean exists(Predicate predicate); (4)
// … more functionality omitted.
}
1 | 查找並返回與 Predicate 相符的單個實體。 |
2 | 查找並返回與 Predicate 相符的所有實體。 |
3 | 返回與 Predicate 相符的實體數量。 |
4 | 返回是否存在與 Predicate 相符的實體。 |
要使用 Querydsl 支援,請在您的儲存庫介面上擴展 QuerydslPredicateExecutor
,如下面的範例所示
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
前面的範例讓您可以使用 Querydsl Predicate
實例編寫類型安全的查詢,如下面的範例所示
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
Web 支援
支援儲存庫程式設計模型的 Spring Data 模組附帶各種 Web 支援。Web 相關組件需要 Spring MVC JAR 位於類別路徑中。其中一些甚至提供與 Spring HATEOAS 的整合。一般來說,整合支援通過在您的 JavaConfig 配置類中使用 @EnableSpringDataWebSupport
註解來啟用,如下面的範例所示
-
Java
-
XML
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
<!-- If you use Spring HATEOAS, register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
@EnableSpringDataWebSupport
註解註冊了一些組件。我們將在本節稍後討論這些組件。它還檢測類別路徑上的 Spring HATEOAS,並註冊整合組件(如果存在)以用於它。
基本 Web 支援
在 前一節中顯示的配置註冊了一些基本組件
-
一個 使用
DomainClassConverter
類別,讓 Spring MVC 從請求參數或路徑變數中解析儲存庫管理的網域類別實例。 -
HandlerMethodArgumentResolver
實作,讓 Spring MVC 從請求參數中解析Pageable
和Sort
實例。 -
Jackson 模組,用於反序列化/序列化
Point
和Distance
等類型,或儲存特定的類型,具體取決於使用的 Spring Data 模組。
使用 DomainClassConverter
類別
DomainClassConverter
類別允許您直接在 Spring MVC 控制器方法簽名中使用網域類型,這樣您就不需要手動通過儲存庫查找實例,如下面的範例所示
@Controller
@RequestMapping("/users")
class UserController {
@RequestMapping("/{id}")
String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
該方法直接接收 User
實例,無需進一步查找。實例可以通過讓 Spring MVC 首先將路徑變數轉換為網域類別的 id
類型,然後最終通過調用在為網域類型註冊的儲存庫實例上的 findById(…)
來訪問實例來解析。
目前,儲存庫必須實作 CrudRepository 才能符合轉換發現的資格。 |
Pageable 和 Sort 的 HandlerMethodArgumentResolver
前一節中顯示的配置片段 也註冊了 PageableHandlerMethodArgumentResolver
以及 SortHandlerMethodArgumentResolver
的實例。該註冊啟用 Pageable
和 Sort
作為有效的控制器方法參數,如下面的範例所示
@Controller
@RequestMapping("/users")
class UserController {
private final UserRepository repository;
UserController(UserRepository repository) {
this.repository = repository;
}
@RequestMapping
String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
前面的方法簽名導致 Spring MVC 嘗試通過使用以下預設配置從請求參數中派生 Pageable
實例
|
您想要檢索的頁面。0-索引,預設為 0。 |
|
您想要檢索的頁面大小。預設為 20。 |
|
應排序的屬性,格式為 |
要自訂此行為,請分別註冊一個實作 PageableHandlerMethodArgumentResolverCustomizer
介面或 SortHandlerMethodArgumentResolverCustomizer
介面的 Bean。將調用其 customize()
方法,讓您更改設定,如下面的範例所示
@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
return s -> s.setPropertyDelimiter("<-->");
}
如果設定現有 MethodArgumentResolver
的屬性不足以滿足您的目的,請擴展 SpringDataWebConfiguration
或啟用 HATEOAS 的等效項,覆寫 pageableResolver()
或 sortResolver()
方法,然後導入您的自訂配置檔案,而不是使用 @Enable
註解。
如果您需要從請求中解析多個 Pageable
或 Sort
實例(例如,對於多個表),您可以使用 Spring 的 @Qualifier
註解來區分它們。然後請求參數必須以 ${qualifier}_
為前綴。以下範例顯示了結果方法簽名
String showUsers(Model model,
@Qualifier("thing1") Pageable first,
@Qualifier("thing2") Pageable second) { … }
您必須填充 thing1_page
、thing2_page
等等。
傳遞到方法中的預設 Pageable
等同於 PageRequest.of(0, 20)
,但是您可以使用 Pageable
參數上的 @PageableDefault
註解來自訂它。
為 Page 建立 JSON 表示
Spring MVC 控制器通常會嘗試最終將 Spring Data 頁面的表示形式呈現給客戶端。雖然可以簡單地從處理程序方法返回 Page
實例,讓 Jackson 按原樣呈現它們,但我們強烈建議不要這樣做,因為底層實作類別 PageImpl
是一個網域類型。這意味著我們可能想要或必須出於不相關的原因更改其 API,而這些更改可能會以破壞性的方式更改產生的 JSON 表示形式。
隨著 Spring Data 3.1 的發布,我們開始通過發出警告日誌來暗示這個問題。我們仍然最終建議利用與 Spring HATEOAS 的整合,以獲得完全穩定且啟用超媒體的頁面呈現方式,以便客戶端可以輕鬆地導航它們。但是從 3.3 版開始,Spring Data 提供了一種方便使用的頁面呈現機制,但不需要包含 Spring HATEOAS。
使用 Spring Data 的 PagedModel
其核心是,該支援由 Spring HATEOAS 的 PagedModel
的簡化版本(Spring Data 版本位於 org.springframework.data.web
套件中)組成。它可以用於包裝 Page
實例,並產生一個簡化的表示形式,該表示形式反映了 Spring HATEOAS 建立的結構,但省略了導航鏈接。
import org.springframework.data.web.PagedModel;
@Controller
class MyController {
private final MyRepository repository;
// Constructor ommitted
@GetMapping("/page")
PagedModel<?> page(Pageable pageable) {
return new PagedModel<>(repository.findAll(pageable)); (1)
}
}
1 | 將 Page 實例包裝到 PagedModel 中。 |
這將產生如下所示的 JSON 結構
{
"content" : [
… // Page content rendered here
],
"page" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
請注意文檔如何包含一個 page
字段,該字段公開了必要的分頁元數據。
全域啟用簡化的 Page 呈現
如果您不想更改所有現有的控制器以添加映射步驟來返回 PagedModel
而不是 Page
,您可以通過如下調整 @EnableSpringDataWebSupport
來啟用將 PageImpl
實例自動翻譯為 PagedModel
@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO)
class MyConfiguration { }
這將允許您的控制器仍然返回 Page
實例,並且它們將自動呈現為簡化的表示形式
@Controller
class MyController {
private final MyRepository repository;
// Constructor ommitted
@GetMapping("/page")
Page<?> page(Pageable pageable) {
return repository.findAll(pageable);
}
}
Page 和 Slice 的超媒體支援
Spring HATEOAS 附帶一個表示模型類別 (PagedModel
/SlicedModel
),它允許使用必要的 Page
/Slice
元數據以及鏈接來豐富 Page
或 Slice
實例的內容,以便客戶端可以輕鬆地導航頁面。Page
到 PagedModel
的轉換由 Spring HATEOAS RepresentationModelAssembler
介面的實作完成,稱為 PagedResourcesAssembler
。同樣,Slice
實例可以使用 SlicedResourcesAssembler
轉換為 SlicedModel
。以下範例顯示了如何使用 PagedResourcesAssembler
作為控制器方法參數,因為 SlicedResourcesAssembler
的工作方式完全相同
@Controller
class PersonController {
private final PersonRepository repository;
// Constructor omitted
@GetMapping("/people")
HttpEntity<PagedModel<Person>> people(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> people = repository.findAll(pageable);
return ResponseEntity.ok(assembler.toModel(people));
}
}
啟用配置(如前面的範例所示)允許 PagedResourcesAssembler
用作控制器方法參數。在其上調用 toModel(…)
具有以下效果
-
Page
的內容成為PagedModel
實例的內容。 -
PagedModel
物件附加了一個PageMetadata
實例,並且它使用來自Page
和底層Pageable
的資訊填充。 -
PagedModel
可能會附加prev
和next
鏈接,具體取決於頁面的狀態。鏈接指向方法映射到的 URI。添加到方法中的分頁參數與PageableHandlerMethodArgumentResolver
的設定相匹配,以確保鏈接稍後可以解析。
假設我們在資料庫中有 30 個 Person
實例。您現在可以觸發請求 (GET localhost:8080/people
) 並查看類似於以下的輸出
{ "links" : [
{ "rel" : "next", "href" : "https://127.0.0.1:8080/persons?page=1&size=20" }
],
"content" : [
… // 20 Person instances rendered here
],
"page" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
此處顯示的 JSON 信封格式不遵循任何正式指定的結構,並且不保證穩定,我們可能會隨時更改它。強烈建議啟用呈現為超媒體啟用的官方媒體類型,由 Spring HATEOAS 支援,例如 HAL。這些可以通過使用其 @EnableHypermediaSupport 註解來啟用。在 Spring HATEOAS 參考文檔中查找更多資訊。 |
匯編器產生了正確的 URI,並且還拾取了預設配置,以將參數解析為即將到來的請求的 Pageable
。這意味著,如果您更改該配置,鏈接會自動遵守更改。預設情況下,匯編器指向調用它的控制器方法,但是您可以通過傳遞自訂 Link
作為構建分頁鏈接的基礎來自訂它,這會重載 PagedResourcesAssembler.toModel(…)
方法。
Spring Data Jackson 模組
核心模組和一些商店特定的模組附帶一組 Jackson 模組,用於 Spring Data 網域使用的類型,例如 org.springframework.data.geo.Distance
和 org.springframework.data.geo.Point
。
一旦啟用 web 支援並且 com.fasterxml.jackson.databind.ObjectMapper
可用,這些模組就會被導入。
在初始化期間,SpringDataJacksonModules
(如 SpringDataJacksonConfiguration
)被基礎架構拾取,以便將聲明的 com.fasterxml.jackson.databind.Module
提供給 Jackson ObjectMapper
。
以下網域類型的資料綁定 Mixin 由通用基礎架構註冊。
org.springframework.data.geo.Distance org.springframework.data.geo.Point org.springframework.data.geo.Box org.springframework.data.geo.Circle org.springframework.data.geo.Polygon
個別模組可能會提供額外的 |
Web 資料綁定支援
您可以使用 Spring Data 投影(在 投影中描述)通過使用 JSONPath 表達式(需要 Jayway JsonPath)或 XPath 表達式(需要 XmlBeam)來綁定傳入的請求有效負載,如下面的範例所示
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
您可以使用前面範例中顯示的類型作為 Spring MVC 處理程序方法參數,或者通過在 RestTemplate
的方法之一上使用 ParameterizedTypeReference
。前面的方法聲明將嘗試在給定文檔中的任何位置查找 firstname
。lastname
XML 查找在傳入文檔的頂層執行。JSON 變體首先嘗試頂層 lastname
,但如果前者不返回值,也會嘗試嵌套在 user
子文檔中的 lastname
。這樣,可以輕鬆地減輕源文檔結構的變化,而無需客戶端調用公開的方法(通常是基於類別的有效負載綁定的缺點)。
支援嵌套投影,如 投影中所述。如果方法返回複雜的非介面類型,則使用 Jackson ObjectMapper
來映射最終值。
對於 Spring MVC,一旦 @EnableSpringDataWebSupport
處於活動狀態且所需的依賴項在類別路徑中可用,必要的轉換器就會自動註冊。對於 RestTemplate
的使用,請手動註冊 ProjectingJackson2HttpMessageConverter
(JSON) 或 XmlBeamHttpMessageConverter
。
有關更多資訊,請參閱規範 Spring Data Examples 儲存庫中的 Web 投影範例。
Querydsl Web 支援
對於那些具有 QueryDSL 整合的商店,您可以從請求查詢字串中包含的屬性派生查詢。
考慮以下查詢字串
?firstname=Dave&lastname=Matthews
給定先前範例中的 User
物件,您可以使用 QuerydslPredicateArgumentResolver
將查詢字串解析為以下值,如下所示
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
當在類別路徑中找到 Querydsl 時,該功能會與 @EnableSpringDataWebSupport 一起自動啟用。 |
將 @QuerydslPredicate
添加到方法簽名中會提供一個隨時可用的 Predicate
,您可以使用 QuerydslPredicateExecutor
運行它。
類型資訊通常從方法的返回類型解析。由於該資訊不一定與網域類型匹配,因此使用 QuerydslPredicate 的 root 屬性可能是個好主意。 |
以下範例顯示如何在方法簽名中使用 @QuerydslPredicate
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate, (1)
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
1 | 將查詢字串參數解析為 User 的匹配 Predicate 。 |
預設綁定如下
-
簡單屬性上的
Object
作為eq
。 -
類似集合屬性上的
Object
作為contains
。 -
簡單屬性上的
Collection
作為in
。
您可以通過 @QuerydslPredicate
的 bindings
屬性或通過使用 Java 8 default methods
並將 QuerydslBinderCustomizer
方法添加到儲存庫介面來自訂這些綁定,如下所示
interface UserRepository extends CrudRepository<User, String>,
QuerydslPredicateExecutor<User>, (1)
QuerydslBinderCustomizer<QUser> { (2)
@Override
default void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(user.username).first((path, value) -> path.contains(value)) (3)
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
bindings.excluding(user.password); (5)
}
}
1 | QuerydslPredicateExecutor 提供對 Predicate 的特定查找器方法的訪問。 |
2 | 在儲存庫介面上定義的 QuerydslBinderCustomizer 會自動拾取並快捷方式 @QuerydslPredicate(bindings=…) 。 |
3 | 將 username 屬性的綁定定義為簡單的 contains 綁定。 |
4 | 將 String 屬性的預設綁定定義為不區分大小寫的 contains 匹配。 |
5 | 從 Predicate 解析中排除 password 屬性。 |
您可以在應用來自儲存庫或 @QuerydslPredicate 的特定綁定之前,註冊一個包含預設 Querydsl 綁定的 QuerydslBinderCustomizerDefaults Bean。 |
儲存庫填充器
如果您使用 Spring JDBC 模組,您可能熟悉使用 SQL 腳本填充 DataSource
的支援。儲存庫層次上提供類似的抽象,儘管它不使用 SQL 作為資料定義語言,因為它必須是商店獨立的。因此,填充器支援 XML(通過 Spring 的 OXM 抽象)和 JSON(通過 Jackson)來定義用於填充儲存庫的資料。
假設您有一個名為 data.json
的檔案,其中包含以下內容
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
您可以使用 Spring Data Commons 中提供的儲存庫命名空間的填充器元素來填充您的儲存庫。要將前面的資料填充到您的 PersonRepository
,請聲明一個類似於以下的填充器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd">
<repository:jackson2-populator locations="classpath:data.json" />
</beans>
前面的聲明導致 data.json
檔案被 Jackson ObjectMapper
讀取和反序列化。
JSON 物件被解組成的類型是通過檢查 JSON 文檔的 _class
屬性來確定的。基礎架構最終選擇適當的儲存庫來處理反序列化的物件。
要改為使用 XML 來定義應該填充儲存庫的資料,您可以使用 unmarshaller-populator
元素。您可以將其配置為使用 Spring OXM 中可用的 XML marshaller 選項之一。有關詳細資訊,請參閱 Spring 參考文檔。以下範例顯示瞭如何使用 JAXB 解組儲存庫填充器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd
http://www.springframework.org/schema/oxm
https://www.springframework.org/schema/oxm/spring-oxm.xsd">
<repository:unmarshaller-populator locations="classpath:data.json"
unmarshaller-ref="unmarshaller" />
<oxm:jaxb2-marshaller contextPath="com.acme" />
</beans>