Spring Data 擴充套件
本節介紹了 Spring Data 的一組擴充套件,這些擴充套件使得 Spring Data 可以在多種上下文中使用。目前,大多數整合都面向 Spring MVC。
Querydsl 擴充套件
Querydsl 是一個框架,它透過其流式 API 支援構建靜態型別的類似 SQL 的查詢。
Querydsl 的維護工作已經放緩,社群已在 OpenFeign 下分支了該專案,地址是 github.com/OpenFeign/querydsl (groupId io.github.openfeign.querydsl )。Spring Data 會盡最大努力支援該分支。 |
幾個 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 支援,請在你的 repository 介面上擴充套件 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 支援
支援 repository 程式設計模型的 Spring Data 模組提供了各種 web 支援。Web 相關元件需要 classpath 中包含 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
註解註冊了一些元件。我們將在本節後面討論這些元件。它還會檢測 classpath 中是否存在 Spring HATEOAS,併為之註冊整合元件(如果存在)。
基礎 Web 支援
上一節所示的配置註冊了一些基本元件
-
一個 使用
DomainClassConverter
類,讓 Spring MVC 可以從請求引數或路徑變數中解析 repository 管理的域類例項。 -
HandlerMethodArgumentResolver
實現,讓 Spring MVC 可以從請求引數中解析Pageable
和Sort
例項。 -
Jackson 模組,用於序列化/反序列化諸如
Point
和Distance
等型別,或特定儲存相關的型別,具體取決於使用的 Spring Data 模組。
使用 DomainClassConverter
類
DomainClassConverter
類允許你在 Spring MVC 控制器方法簽名中直接使用域型別,這樣你就不必手動透過 repository 查詢例項,如下例所示
@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
型別,然後透過對為該域型別註冊的 repository 例項呼叫 findById(…)
來訪問例項,從而可以解析該例項。
目前,repository 必須實現 CrudRepository 才能符合轉換為發現的條件。 |
Pageable 和 Sort 的 HandlerMethodArgumentResolvers
上一節所示的配置片段也註冊了一個 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 頁面的表示。雖然可以直接從 handler 方法返回 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
欄位,公開了必要的 pagination 元資料。
全域性啟用簡化的 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
。類似地,可以使用 SlicedResourcesAssembler
將 Slice
例項轉換為 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。新增到方法的 pagination 引數與PageableHandlerMethodArgumentResolver
的設定匹配,以確保稍後可以解析連結。
假設我們在資料庫中有 30 個 Person
例項。你現在可以觸發一個請求(GET localhost:8080/people
),並看到類似於以下的輸出
{ "links" : [
{ "rel" : "next", "href" : "https://: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 模組
核心模組以及一些特定儲存模組提供了用於 Spring Data 域中使用的型別的 Jackson 模組集,例如 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 projections(在 投影 中描述)來透過使用 JSONPath 表示式(需要 Jayway JsonPath)或 XPath 表示式(需要 XmlBeam)來繫結傳入的請求 payloads,如下例所示
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
你可以將前面示例中所示的型別用作 Spring MVC handler 方法引數,或者透過在 RestTemplate
的某個方法上使用 ParameterizedTypeReference
來使用它。前面的方法宣告會嘗試在給定文件中的任何位置查詢 firstname
。lastname
XML 查詢是在傳入文件的頂層執行的。它的 JSON 變體首先嚐試頂層的 lastname
,如果前者沒有返回值,也會嘗試巢狀在 user
子文件中的 lastname
。這樣,可以輕鬆地緩解源文件結構變化的影響,而無需客戶端呼叫暴露的方法(這通常是基於類的 payload 繫結的一個缺點)。
支援巢狀投影,如 投影 中所述。如果方法返回複雜、非介面型別,則使用 Jackson ObjectMapper
來對映最終值。
對於 Spring MVC,只要 @EnableSpringDataWebSupport
處於活動狀態且 classpath 中存在所需的依賴項,就會自動註冊必要的轉換器。對於與 RestTemplate
一起使用,請手動註冊 ProjectingJackson2HttpMessageConverter
(JSON) 或 XmlBeamHttpMessageConverter
。
更多資訊,請參閱典型的 Spring Data Examples repository 中的 web projection 示例。
Querydsl Web 支援
對於那些集成了 Querydsl 的儲存,你可以從 Request
查詢字串中包含的屬性派生查詢。
考慮以下查詢字串
?firstname=Dave&lastname=Matthews
假設使用前面示例中的 User
物件,你可以透過使用 QuerydslPredicateArgumentResolver
將查詢字串解析為以下值,如下所示
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
當 classpath 中找到 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
方法新增到 repository 介面來自定義這些繫結,如下所示
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 的特定 finder 方法的訪問。 |
2 | 在 repository 介面上定義的 QuerydslBinderCustomizer 會自動被識別,並作為 @QuerydslPredicate(bindings=…) 的快捷方式。 |
3 | 將 username 屬性的繫結定義為一個簡單的 contains 繫結。 |
4 | 將 String 屬性的預設繫結定義為不區分大小寫的 contains 匹配。 |
5 | 從 Predicate 解析中排除 password 屬性。 |
在應用 repository 或 @QuerydslPredicate 中的特定繫結之前,你可以註冊一個持有預設 Querydsl 繫結的 QuerydslBinderCustomizerDefaults bean。 |
Repository Populators
如果你使用 Spring JDBC 模組,你可能熟悉使用 SQL 指令碼填充 DataSource
的支援。在 repositories 層面也有類似的抽象,儘管它不使用 SQL 作為資料定義語言,因為它必須是儲存無關的。因此,populators 支援 XML(透過 Spring 的 OXM 抽象)和 JSON(透過 Jackson)來定義用於填充 repository 的資料。
假設你有一個名為 data.json
的檔案,其內容如下
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
你可以使用 Spring Data Commons 中提供的 repository 名稱空間的 populator 元素來填充你的 repository。要將前面資料填充到你的 PersonRepository
,請宣告一個類似於以下的 populator
<?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
屬性來確定的。基礎設施最終會選擇適當的 repository 來處理已反序列化的物件。
要改為使用 XML 定義用於填充 repository 的資料,可以使用 unmarshaller-populator
元素。你可以將其配置為使用 Spring OXM 中可用的 XML marshaller 選項之一。詳細資訊請參閱 Spring 參考文件。以下示例展示瞭如何使用 JAXB 解組 repository populator
<?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>