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 支援,請在您的 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 相關的組件需要 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 從請求參數或路徑變數解析 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 的 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 範例 repository 中的 web 投影範例。
Querydsl Web 支援
對於那些具有 QueryDSL 整合的儲存區,您可以從 Request
查詢字串中包含的屬性衍生查詢。
考慮下列查詢字串
?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
方法新增至 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 屬性。 |
您可以註冊 QuerydslBinderCustomizerDefaults bean,以在套用來自 repository 或 @QuerydslPredicate 的特定繫結之前,保留預設的 Querydsl 繫結。 |
Repository Populators
如果您使用 Spring JDBC 模組,您可能熟悉使用 SQL 腳本填入 DataSource
的支援。 repository 層級提供類似的抽象概念,儘管它不使用 SQL 作為資料定義語言,因為它必須與儲存區無關。 因此,populator 支援 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>
先前的宣告會導致 Jackson ObjectMapper
讀取和還原序列化 data.json
檔案。
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>