Spring Data 擴充功能

本節說明一組 Spring Data 擴充功能,這些擴充功能可在各種情境中啟用 Spring Data 的使用。目前,大多數整合都以 Spring MVC 為目標。

Querydsl 擴充功能

Querydsl 是一個框架,可透過其流暢的 API 建構靜態型別的類 SQL 查詢。

幾個 Spring Data 模組透過 QuerydslPredicateExecutor 提供與 Querydsl 的整合,如下列範例所示

QuerydslPredicateExecutor 介面
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,如下列範例所示

儲存庫上的 Querydsl 整合
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 註解來啟用,如下列範例所示

啟用 Spring Data Web 支援
  • 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 支援

在 XML 中啟用 Spring Data Web 支援

前一節中顯示的設定會註冊一些基本元件

  • 一個 使用 DomainClassConverter 類別,讓 Spring MVC 從請求參數或路徑變數解析儲存庫管理的網域類別的實例。

  • HandlerMethodArgumentResolver 實作,讓 Spring MVC 從請求參數解析 PageableSort 實例。

  • Jackson 模組,用於序列化/還原序列化類型,例如 PointDistance,或儲存特定類型,具體取決於使用的 Spring Data 模組。

使用 DomainClassConverter 類別

DomainClassConverter 類別可讓您直接在 Spring MVC 控制器方法簽章中使用網域類型,因此您無需透過儲存庫手動查閱實例,如下列範例所示

在方法簽章中使用網域類型的 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 的實例。註冊啟用 PageableSort 作為有效的控制器方法引數,如下列範例所示

將 Pageable 用作控制器方法引數
@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 實例

表 1. 針對 Pageable 實例評估的請求參數

page

您要擷取的頁面。以 0 為索引,預設為 0。

size

您要擷取的頁面大小。預設為 20。

sort

應依格式 property,property(,ASC|DESC)(,IgnoreCase) 排序的屬性。預設排序方向為區分大小寫的遞增。如果您想要切換方向或大小寫敏感度,請使用多個 sort 參數 — 例如,?sort=firstname&sort=lastname,asc&sort=city,ignorecase

若要自訂此行為,請分別註冊實作 PageableHandlerMethodArgumentResolverCustomizer 介面或 SortHandlerMethodArgumentResolverCustomizer 介面的 Bean。將會呼叫其 customize() 方法,讓您變更設定,如下列範例所示

@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
    return s -> s.setPropertyDelimiter("<-->");
}

如果設定現有 MethodArgumentResolver 的屬性不足以滿足您的目的,請擴充 SpringDataWebConfiguration 或啟用 HATEOAS 的對等項目,覆寫 pageableResolver()sortResolver() 方法,並匯入您自訂的設定檔,而不是使用 @Enable 註解。

如果您需要從請求解析多個 PageableSort 實例(例如,針對多個表格),您可以使用 Spring 的 @Qualifier 註解來區分彼此。然後,請求參數必須以 ${qualifier}_ 為前綴。下列範例顯示產生的方法簽章

String showUsers(Model model,
      @Qualifier("thing1") Pageable first,
      @Qualifier("thing2") Pageable second) { … }

您必須填入 thing1_pagething2_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);
  }
}

PageSlice 的超媒體支援

Spring HATEOAS 隨附表示法模型類別 (PagedModel/SlicedModel),可讓您使用必要的 Page/Slice 中繼資料以及連結來豐富 PageSlice 實例的內容,讓用戶端可以輕鬆地瀏覽頁面。PagePagedModel 的轉換由 Spring HATEOAS RepresentationModelAssembler 介面的實作完成,稱為 PagedResourcesAssembler。同樣地,可以使用 SlicedResourcesAssemblerSlice 實例轉換為 SlicedModel。下列範例顯示如何將 PagedResourcesAssembler 用作控制器方法引數,因為 SlicedResourcesAssembler 的工作方式完全相同

將 PagedResourcesAssembler 用作控制器方法引數
@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 可能會附加 prevnext 連結,具體取決於頁面的狀態。連結指向方法對應到的 URI。新增至方法的頁碼參數符合 PageableHandlerMethodArgumentResolver 的設定,以確保稍後可以解析連結。

假設我們的資料庫中有 30 個 Person 實例。您現在可以觸發請求 (GET localhost:8080/people) 並查看類似於以下的輸出

{ "links" : [
    { "rel" : "next", "href" : "http://localhost: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.Distanceorg.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

個別模組可能會提供額外的 SpringDataJacksonModules
如需更多詳細資訊,請參閱商店特定章節。

Web 資料繫結支援

您可以使用 Spring Data 投影(在 投影中說明)透過使用 JSONPath 運算式(需要 Jayway JsonPath)或 XPath 運算式(需要 XmlBeam)來繫結傳入的請求酬載,如下列範例所示

使用 JSONPath 或 XPath 運算式的 HTTP 酬載繫結
@ProjectedPayload
public interface UserPayload {

  @XBRead("//firstname")
  @JsonPath("$..firstname")
  String getFirstname();

  @XBRead("/lastname")
  @JsonPath({ "$.lastname", "$.user.lastname" })
  String getLastname();
}

您可以將前述範例中顯示的類型用作 Spring MVC 處理常式方法引數,或透過在 RestTemplate 的其中一種方法上使用 ParameterizedTypeReference。前述方法宣告會嘗試在給定文件中尋找任何位置的 firstnamelastname XML 查閱是在傳入文件的最上層執行。JSON 變體會先嘗試最上層 lastname,但如果前者未傳回值,也會嘗試巢狀在 user 子文件中的 lastname。這樣一來,來源文件的結構變更可以輕鬆地減輕,而無需用戶端呼叫公開的方法(通常是基於類別的酬載繫結的缺點)。

支援巢狀投影,如 投影中所述。如果方法傳回複雜的非介面類型,則會使用 Jackson ObjectMapper 來對應最終值。

對於 Spring MVC,一旦 @EnableSpringDataWebSupport 處於作用中狀態,且所需的相依性在類別路徑上可用,必要的轉換器就會自動註冊。若要與 RestTemplate 搭配使用,請手動註冊 ProjectingJackson2HttpMessageConverter (JSON) 或 XmlBeamHttpMessageConverter

如需更多資訊,請參閱標準 Spring Data 範例儲存庫中的 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 來執行它。

類型資訊通常從方法的傳回類型解析。由於該資訊不一定與網域類型相符,因此使用 QuerydslPredicateroot 屬性可能是個好主意。

下列範例顯示如何在方法簽章中使用 @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

預設繫結如下

  • 簡單屬性上的 Objecteq

  • 類似集合的屬性上的 Objectcontains

  • 簡單屬性上的 Collectionin

您可以透過 @QuerydslPredicatebindings 屬性來自訂這些繫結,或透過使用 Java 8 預設方法 並將 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 屬性。
您可以註冊 QuerydslBinderCustomizerDefaults Bean,在套用來自儲存庫或 @QuerydslPredicate 的特定繫結之前,保留預設 Querydsl 繫結。

儲存庫填充器

如果您使用 Spring JDBC 模組,您可能熟悉使用 SQL 指令碼填充 DataSource 的支援。儲存庫層級上提供類似的抽象,儘管它不使用 SQL 作為資料定義語言,因為它必須與商店無關。因此,填充器支援 XML(透過 Spring 的 OXM 抽象)和 JSON(透過 Jackson)來定義要用來填充儲存庫的資料。

假設您有一個名為 data.json 的檔案,其中包含下列內容

JSON 中定義的資料
[ { "_class" : "com.acme.Person",
 "firstname" : "Dave",
  "lastname" : "Matthews" },
  { "_class" : "com.acme.Person",
 "firstname" : "Carter",
  "lastname" : "Beauford" } ]

您可以使用 Spring Data Commons 中提供的儲存庫命名空間的填充器元素來填充您的儲存庫。若要將上述資料填充到您的 PersonRepository,請宣告類似於以下的填充器

宣告 Jackson 儲存庫填充器
<?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 還原編組儲存庫填充器

宣告還原編組儲存庫填充器(使用 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>