1. 程式人生 > 實用技巧 >Spring Data JPA 學習筆記1 - JPA與Spring Data

Spring Data JPA 學習筆記1 - JPA與Spring Data

標記【跳過】的未來完善

1 理解JPA

1.1 什麼是持久化?

當一個軟體關閉的時候,軟體內儲存的狀態資料還能在下次開啟時被恢復,這就是持久化。物件持久化是指每個獨立的物件的生命週期都能不依賴應用程式程序,比如將物件儲存到資料庫或者在以後能被重新建立。在Java當中,持久化是指使用SQL語句在資料庫種對映和儲存物件。

1.2 正規化不匹配問題

Java中物件的儲存和傳統SQL資料庫中儲存資訊的方式完全不同,既然我們要把Java物件儲存到資料庫中,我們就需要解決Java物件與資料庫資料條目的正規化不匹配問題。物件與資料庫資料正規化不匹配主要有以下幾個方面

  1. 資訊粒度不匹配

    Java能很容易的儲存一個含有地址物件屬性的使用者物件,地址物件可以包含很多屬性。Java的這種結構很難確定好資料粒度,而SQL資料庫以表的形式儲存最小資料粒度就是一個行鍵和列確定的單元格的值。

  2. 繼承/實現關係不匹配

    SQL資料庫沒辦法實現面向物件中的繼承和多型性。

  3. 比較方式不匹配

    SQL資料庫用主鍵來區分不同的資料,而Java使用==equals()或者compareTo()等來確定兩個物件是否一致。

  4. 關聯方式不匹配

    面向物件程式通過物件引用來實現關聯,但是關係型資料庫使用外來鍵來關聯兩個例項。

    面向物件中的關聯是有向的,如果想要雙向需要在關聯的兩頭都宣告關聯。關係型資料庫完全沒有關聯方向,需要關聯則需要joinprojection操作

    Java可以很容易實現多對多關聯,比如一個學生有多個老師,這些老師同時也教多個學生,只需要兩邊都寫個對方類物件的集合就可以,但是關係型資料庫中需要一張單獨的關聯表

  5. 瀏覽資料方式不匹配

    Java中使用儲存的指標來在多個類的物件之間獲取資料,而SQL資料庫需要多個表的join來獲得,如果類物件的關聯網比較複雜就需要join更多張表

1.3 ORM和JPA

ORM(Object/Relational Mapping)物件關係對映是通過使用描述物件和資料庫之間對映的元資料,將面嚮物件語言程式中的物件資料和物件之間的關聯自動持久化到關係資料庫中的一種操作。

JPA(Java Persistence API)Java持久化層API是Java語言中實現ORM的一個標準,實現JPA規範的ORM框架,可以幫助實現物件的持久化操作。

JPA規範定義了:

  • 一套具體對映元資料的標準,元資料指明瞭持久化類結構和他們具有的屬性與資料庫表之間的關聯
  • 一些用於執行CRUD操作的API介面,省去編寫重複JDBC和SQL程式碼的麻煩
  • 一套查詢語言
  • 定義了帶有事務特徵的持久化引擎如何執行髒資料檢查、關聯查詢以及其他的優化函式

Hibernate就是一套實現JPA標準的Java持久化框架,Spring Data JPA就是基於Hibernate實現JPA規範,並提供了簡化開發過程的一些功能

2 Spring Data

2.1 核心概念

Spring Data是一個致力於減少資料訪問層DAO開發量的專案,開發者只需要定義資料倉庫介面繼承Spring Data定義好的具有CRUD等資料訪問功能的介面,或者按照Spring Data定義的由動詞、可選主題、關鍵詞By和斷言組成的領域特定語言編寫的方法名讓Spring Data自動生成具體操作程式碼和語句,實現程式設計式資料訪問

Spring Data倉庫抽象的核心介面是Repository<T, ID>,它接受領域類和領域類ID型別作為型別引數,是一個用於獲取後續工作所要使用的型別並且幫助你去了解其他擴充套件介面的標記介面,定義的資料倉庫介面繼承它就能實現Spring Data根據方法名生成查詢操作的功能。以下是幾個比較重要的子介面

  • CurdRepository:為被管理實體實現了許多複雜的CURD功能

    public interface CrudRepository<T, ID> extends Repository<T, ID> {
    
      <S extends T> S save(S entity);	// 儲存實體       
      Optional<T> findById(ID primaryKey); 	// 使用ID引數找到實體
      Iterable<T> findAll();	// 獲取全部實體
      long count();	// 所有實體數量
      void delete(T entity);	// 刪除指定實體               
      boolean existsById(ID primaryKey);	// 是否存在指定ID的實體   
        
      // … 還有其他的方法
    }
    
  • PagingAndSortingRepository:為findAll()獲取實體提供分頁能力

    public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
    
      Iterable<T> findAll(Sort sort);
      Page<T> findAll(Pageable pageable);	// 傳入PageRequest.of(0, 20)獲取第一頁共20條實體
        
    }
    
  • JpaRepository:實現了JPA規範的相關方法

    public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
        
      void flush();
      <S extends T> S saveAndFlush(S entity);
      void deleteInBatch(Iterable<T> entities);
      void deleteAllInBatch();
      T getOne(ID id);
      
      // 覆寫了一些上層介面的方法
    }
    

2.2 使用步驟

  1. 定義一個繼承Repository或者它子介面的介面,傳入領域類和領域類ID型別

    interface PersonRepository extends Repository<Person, Long> { … }
    
  2. 在接口裡宣告符合Spring Data方法宣告或介面內已有的方法

    interface PersonRepository extends Repository<Person, Long> {
      List<Person> findByLastname(String lastname);
    }
    
  3. 如果是使用Spring則可以用Java配置的方式建立這些介面的代理,如果是新版Spring Boot那就什麼也不用做了

    @EnableJpaRepositories
    class Config { … }
    
  4. 然後就可以直接注入自定義的PersonRepository使用了

    class SomeService {
    
      private final PersonRepository repository;
    
      SomeClient(PersonRepository repository) {	// 構造器方式注入
        this.repository = repository;
      }
    
      void doSomething() {
        List<Person> persons = repository.findByLastname("Lee");
      }
    }
    

下面是每個步驟的詳細說明

2.3 定義介面

使用Spring Data的第一步就是定義對應每個領域類的資料倉庫(Repository)介面,這個介面必須繼承Repository<T, ID>介面並傳入領域類與領域類主鍵型別。如果想要暴露一些已經定義好的CRUD方法,轉而繼承CrudRepository或者其他介面。

2.3.1 倉庫定義詳解

正常情況下定義的倉庫介面會繼承RepositoryCrudRepository或者PagingAndSortingRepository。如果不想繼承Spring Data已經定義好的介面可以僅加上@RepositoryDefinition並設定domainClassidClass屬性,這個註釋等同於該介面繼承了Repository<T, ID>,註釋上的兩個屬性對應Repository介面的TID引數

在繼承例如CrudRepository介面時,如果希望僅暴露部分介面,可以複製介面內的方法到一箇中間介面內,這個介面可以新增@NoRepositoryBean註釋來通知Spring不為該中間介面生成Bean

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {

  Optional<T> findById(ID id);

  <S extends T> S save(S entity);
}

interface UserRepository extends MyBaseRepository<User, Long> {
  User findByEmailAddress(EmailAddress emailAddress);
}

倉庫定義的方法會被路由到你選擇的Spring Data儲存的基礎資料倉庫上,當你使用Spring Data JPA時這個基礎倉庫會是SimpleJpaRepository,例子中的UserRepository就有了用ID查詢使用者、儲存使用者、用郵箱地址找到使用者的能力

2.3.2 與多個Spring Data模組配合

如果專案需要用到多個Spring Data模組,比如既想用Spring Data JPA也想用Spring Data MongoDB,Spring Data在探測到多個模組時就會進入嚴格模式,利用Repository的定義來區分哪個倉庫用哪個模組。

  1. 如果自定義的倉庫定義直接繼承模組對應的倉庫介面,則放到指定模組的候選裡。介面例如JPA的JpaRepository
  2. 如果自定義倉庫的領域類被特定模組的註解註釋了,則被放到指定模組的候選裡。註釋例如JPA的@Entity和Spring Data MongoDB和Elasticsearch的@Document註解
// 針對Person是西安一個repository介面
interface PersonRepository extends Repository<Person, Long> { … }

@Entity	// 使用JPA的@Entity註釋,Spring Data會將該領域類對應介面給Spring Data JPA進行管理
class Person { … }

// 針對User實現一個repository介面
interface UserRepository extends Repository<User, Long> { … }

@Document	// 使用@Document註釋,Spring Data會將該領域類對應介面給Spring Data MongoDB進行管理
class User { … }

但是上述情況有例外,當使用中間介面時,SpriDat無法完美分辨出針對同一個領域類的繼承同樣介面的中間介面實現和直接實現,所以避免這種情況。

多個模組整合時還可以在啟用對應倉庫時新增basePackage註釋

@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }

2.4 定義查詢方法

Spring Data的資料倉庫介面代理有兩種將查詢方法轉換為實際查詢的途徑,分別是直接解析方法名和解析定義好的查詢。

2.4.1 查詢方法選擇

可以通過在@Enable...Repository註釋上增加queryLookupStrategy屬性來設定特定的Spring Data模組如何解析查詢方法。這個屬性有三個可以選擇的值:

  • CREATE:使用方法名生成
  • USE_DECLARED_QUERY:使用開發者已經生命好的查詢語句,如果找不到將會拋錯
  • CREATE_IF_NOT_FOUND:預設使用的方法,結合了兩者,首先會查詢已經宣告好的查詢語句,如果找不到就解析方法名

2.4.2 查詢語句生成

Spring Data資料倉庫架構內的查詢語句生成機制會根據需要自動生成對應查詢語句。首先它會去掉方法名的find...Byread...Byquery...Bycount...Byget...By等字首詞並解析方法名省下的部分。第一個By會作為實際查詢規則的開頭,後面增加的語句可以使用AndOr來連線,下面是一些查詢語句的例子

interface PersonRepository extends Repository<Person, Long> {

  // 使用EmailAddress屬性對應欄位和Lastname屬性對應欄位查詢Person
  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // 使用SQL中的Distinct標籤
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // 不區分大小寫(IgnoreCase)的查詢Lastname
  List<Person> findByLastnameIgnoreCase(String lastname);
  // 所有可以不區分大小寫的引數都不區分
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // 使用OrderBy排序,Asc是升序,Desc是降序
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

實際的查詢功能與用到的Spring Data模組有關,下面是一些通用的語句寫法

  • And或者Or連線條件,使用BetweenLessThanGreaterThanLike等來寫條件
  • IgnoreCase忽略大小寫,可以分屬性寫比如findByLastnameIgnoreCase(String Lastname)也可以應用於所有字串引數findByLastnameAndFirstnameAllIgnoreCase(String Lastname, String Firstname)
  • OrderBy語句用來為查詢結果排序,可以選擇Asc升序或者Desc降序

2.4.3 屬性定義的寫法

查詢方法內屬性名稱只跟實體的屬性名字有關,如果是多層巢狀直接一層一層寫就可以了,比如下方的例子中Person擁有物件屬性Address,約束是Address內的Zipcode物件,這就形成了查詢Person.address.zipCode

List<Person> findByAddressZipCode(ZipCode zipCode);

Spring Data會首先將AddressZipCode作為一個完整的屬性名來解析,如果解析不成功,解析演算法會從欄位右側的第一個大寫字母開始分開欄位來解析(解析為AddressZipCode),如果第一步解析失敗則將從左部分開(解析為AddressZipCode)並迴圈這樣做。如果你覺得這樣太麻煩,也可以直接用_下劃線將屬性名稱分開。

List<Person> findByAddress_ZipCode(ZipCode zipCode);

由於Spring Data使用下劃線解析方法名的實現,編寫實體類的時候不能使用_下劃線在屬性名內,可以使用駝峰命名方式。

2.4.4 解析引數方式

除了正常上方例子展示的傳參方法(方法名內定義屬性對應一個方法的引數),Spring Data還會解析額外的像Pageable分頁引數和Sort排序引數等動態分頁分類查詢引數如下面的例子所示。

// 注意新增Pagebale引數的返回值可以用Page、Slice、List來包裝
Page<User> findByLastname(String lastname, Pageable pageable);

// 如果資料庫內可能的資料條目比較大,使用Slice效能更好,使用Page將呼叫Count語句統計資料庫內資料條數,而Slice不具有這個資訊也就不需要呼叫Count
Slice<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Sort sort);

List<User> findByLastname(String lastname, Pageable pageable);

特別注意,如果方法定義了PageableSort引數時在某些情況下你不想使用分頁和排序了,請不要向方法傳入null,正確的做法是使用Sort.unsorted()Pageable.unpaged()構建引數來告知Spring Data不排序和分頁。

2.4.4.1 構建排序分頁

構建排序物件非常容易,一個排序物件可以結合多個排序方式

Sort sort = Sort.by("firstname").ascending()
  .and(Sort.by("lastname").descending());

不過上述傳參排序方式只用屬性名可能會出問題,可以使用下面的方式指定類來確保型別安全

TypedSort<Person> person = Sort.sort(Person.class);

TypedSort<Person> sort = person.by(Person::getFirstname).ascending()
  .and(person.by(Person::getLastname).descending());

如果使用的Spring Data模組支援Querydsl,也可以使用生成的元模型型別來定義

QSort sort = QSort.by(QPerson.firstname.asc())
  .and(QSort.by(QPerson.lastname.desc()));

Pageable的構建可以通過Spring MVC引數直接獲取或者使用PageRequest.of()定義一個實現了Pageable介面的PageRequest物件。

傳給Pageable的引數有三個page從0開始的頁碼、size每一頁的大小預設20、sort排序方法(在請求中傳sort=firstname&sort=lastname,desc引數表示在按firstname正序排列基礎上按lastname倒序排列)

@RequestMapping("list")
public Page<T> getEntryByPageable(
    @PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC) Pageable pageable
) {
    return dao.findAll(pageable);
}
PageRequest pageRequest = PageRequest.of(0, 5);	// 構造查詢第0頁每頁5個的PageRequest,這個物件可以直接傳給宣告Pageable引數的方法
PageRequest pageRequest = PageRequest.of(0, 20, sort);	//	還可以傳sort物件進去	

2.4.5 限制查詢結果數

查詢方法可以使用相同效果的first或者top關鍵詞來限制查詢條目,firsttop後面可以新增數字來限制查詢條目數量,如果加了firsttop預設的返回條目會是1。結合這倆關鍵詞與Sort或者Pageable可以實現返回末尾元素

User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

Slice<User> findTop3ByLastname(String lastname, Pageable pageable);

2.4.6 返回集合和迭代陣列

一個返回多個元素的Repository方法可以使用IterableListSet,同時也提供Spring Data的Streamable一個Iterable的擴充套件型別支援。

2.4.6.1 返回流型別

Streamable可以代替Iterable或者其他集合型別,它提供了方便的非並行Stream支援,提供了filter()map()也可以方便的與其他Streamable集合合併成一個集合

interface PersonRepository extends Repository<Person, Long> {
  Streamable<Person> findByFirstnameContaining(String firstname);
  Streamable<Person> findByLastnameContaining(String lastname);
}

// 合併了兩個查詢結果
Streamable<Person> result = repository.findByFirstnameContaining("av")
    .and(repository.findByLastnameContaining("ea"));
2.4.6.2自定義流包裝型別

為返回多個元素的集合型別提供專屬的包裝型別是很常見的,我們可以通過實現以下兩個標準來讓Spring Data返回自定義的包裝型別

  • 這個類必須實現Streamable介面
  • 這個類必須有一個接收Streamable引數名為of()valueOf()的構造器或靜態工廠函式
class Product { 	// 單個產品元素
  MonetaryAmount getPrice() { … }
}

@RequiredArgConstructor(staticName = "of")	// 讓Lombok為我們生成一個of()建構函式
class Products implements Streamable<Product> { 	// 實現Streamable介面的集合型別

  private Streamable<Product> streamable;

  public MonetaryAmount getTotal() {	// 向外暴露一些介面
    return streamable.stream()
      .map(Priced::getPrice)
      .reduce(Money.of(0), MonetaryAmount::add);
  }
}

interface ProductRepository implements Repository<Product, Long> {
  Products findAllByDescriptionContaining(String text); 	// 直接用Products返回就可以了
}

2.4.7 Null值的處理

Repository裡的CRUD方法可以使用Optional返回集合例項來解決可能的空值問題,Spring Data還支援返回com.google.common.base.Optionalscala.Optionio.vavr.control.Option型別。當Repository內的方法不返回包裝型別而是直接返回實體時,如果未查詢到就會返回null。當返回值被定義為集合、包裝型別、流型別為空時將會返回空集合而不是null

2.4.7.1 處理Null值的註解

Spring框架本身提供了一些處理Null值的註釋,當不符合規範時將拋錯

  • @NonNullApi:用在包上例如package com.exmple語句上,宣告預設接收的引數和返回值不接受null值
  • @NonNull:用在引數或返回值上,宣告不接受null
  • @Nullable:用在引數或返回值上,宣告接受null

為了啟動執行時的null安全檢查,需要像下方在package-info.java新增@NonNullApi

@org.springframework.lang.NonNullApi
package com.acme;

不過當然最好的方式是使用Optional或集合、包裝、流型別,這樣就會返回一個空的集合物件,下方展示了處理Null值的不同方式

package com.acme;	// 這個包首先定義了上方的註釋,然後就可以開始null檢查了                              

import org.springframework.lang.Nullable;

interface UserRepository extends Repository<User, Long> {

  // 當查詢無結果時丟擲EmptyResultDataAccessException,當引數為null時丟擲IllegalArgumentException
  User getByEmailAddress(EmailAddress emailAddress);                    

  @Nullable	// 接受null的返回值
  User findByEmailAddress(@Nullable EmailAddress emailAdress);	// 接受null引數

  // 如果查詢無結果時返回Optional.empty(),當引數為null時丟擲IllegalArugmentException
  Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); 
}

4.4.7 kotlin【跳過】

2.4.8 查詢返回流

查詢方法返回值可以是Java 8的Stream<T>

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

因為Stream必須在使用後掛壁,所以你可以手動呼叫close()或使用try-with-resource特性來使用流

try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach(…);
}

4.4.9 非同步查詢【跳過】

2.5 建立倉庫例項

4.5.1 XML配置【跳過】

2.5.1 JavaConfig配置

在一個Java配置類上使用Spring Data模組對應的@Enable...Repository註解就可以開啟對應資料倉庫支援

@Configuration
@EnableJpaRepositories("com.acme.repositories")	// 使用Spring Data Jpa
class ApplicationConfiguration {

  @Bean
  EntityManagerFactory entityManagerFactory() {
    // …
  }
}

4.6自定義Spring Data Repository實現【跳過】

2.6 從聚集根處訂閱事件

當我們執行完一些資料操作後經常需要執行一些其他業務流程,這時候就需要一些方法來讓我們獲取到資料操作事件。Spring Data倉庫管理的實體是聚集根,在聚集根處可以發出領域事件。Spring Data提供了@DomainEvents註解,這個註解可以用在聚集根上來獲取資料處理事件。

class User {

    @DomainEvents 
    Collection<Object> domainEvents() {
        // … 返回一個或多個發出的事件
        return new UserSaveEvent();
    }

    @AfterDomainEventPublication	// 可選
    void callbackMethod() {
       // … 可以在這執行一些領域事件發出後執行的回撥
    }
}

class UserSaveEvent {}

/** 接受User發出的型別為UserSaveEvent的DomainEvents事件
  * phase有BEFORE_COMMIT、AFTER_COMMIT、AFTER_ROLLBACK、AFTER_COMPLETION
 **/
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)	
public void event(UserSaveEvent event){
    // 這裡就拿到event了
    userRepository.getOne();
}

2.7 Spring Data擴充套件

2.7.1 SpringMVC支援

未使用Spring Boot組合Spring Data與Spring MVC需要在配置類上新增如下注釋

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}

@EnableSpringDataWebSupport註解註冊會自動註冊一些下方提到的元件

2.7.1.1 基本Web支援

開啟上方提到的註釋會註冊下方几個基礎元件:

  • DomainClassConverter給Spring MVC在請求到達時解析在請求引數或路徑引數傳遞的資料倉庫管理的領域類的能力
  • HandlerMethodArgumentResolver的實現,讓Spring MVC有解析PageableSort例項請求引數的能力

DomainClassConverter給予Spring MVC從請求引數裡直接解析出領域類的能力,這樣你就不需要手動與倉庫管理的領域類進行互動了

@Controller
@RequestMapping("/users")
class UserController {

  @RequestMapping("/{id}")
  String showUserForm(
      @PathVariable("id") User user, // 直接從路徑引數裡解析出User領域類
      Model model
  ) {
    model.addAttribute("user", user);
    return "userForm";
  }
}

上方的例子中,引數僅傳入了一個使用者的id,由於DomainClassConverter的存在,傳入引數將會觸發資料倉庫執行findById()操作,並最終獲取資料倉庫裡儲存的對應id的例項,但該資料倉庫必須要實現CrudRepository介面

2.7.1.2 分頁排序支援

在註冊的主鍵中還有兩個針對HandlerMethodArgumentResolver的實現,分別是給予Spring MVC處理分頁能力的PageableHandlerMethodArgumentResolverSortHandlerMethodArgumentResolver。這兩個介面實現讓Spring MVC的Controller有了解析PageableSort引數的能力

@Controller
@RequestMapping("/users")
class UserController {

  private final UserRepository repository;

  UserController(UserRepository repository) {
    this.repository = repository;
  }

  @RequestMapping
  String showUsers(
      Model model, 
      Pageable pageable	// Spring MVC現在有能力直接解析Pageable引數
  ) {
    model.addAttribute("users", repository.findAll(pageable));	// 直接使用pageable物件查資料
    return "users";
  }
}

Pageable介面接受page從0開始的頁碼數、size預設為20的每頁元素數量、sort排序方式引數,在前面有提到過具體使用方法。

如果想自定義解析器,則可以建立一個實現PageableHandlerMethodArgumentResolverCustomizerSortHandlerMethodArgumentResolverCustomizer介面的Bean,介面內的customize()方法會被呼叫給予你修改設定的能力

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

更進一步的完全定製可以繼承SpringDataWebConfiguration重寫父類內pageableResolver()或者sortResolver()方法,引入自定義的配置檔案而不是使用@Enabel*註解

如果查詢多個表時需要傳入多個PageableSort引數,則需要使用Spring的@Qualifier註解,此時傳入資料的引數名需要帶有${qualifier}_字首

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

在上例中我們給第一個Pageable傳參page時,寫法是thing1_page=x

預設的Pageable等於一個PageRequest.of(0, 20)生成的PageRequest物件,這個預設值可以通過在Pageable引數上新增@PageableDefault註解並傳入屬性的方式進行定製。

4.8.2 - 超媒體支援【跳過】

2.7.1.3 資料繫結支援

Spring Data投影(Projections)可以使用JSONPath表達(需要Jayway JsonPath表示式)直接繫結到請求引數上

@ProjectedPayload
public interface UserPayload {

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

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

上方的投影型別可以被用作Spring MVC方法的引數,上例中Spring MVC會從JSON文件的所有節點下查詢firstname引數、從根節點如果沒有找到再去user子節點下查詢lastname引數,上述內容可以在Jayway JsonPath官方文件中找到

投影的具體內容可以在Spring Data JPA的投影中找到,如果方法返回一個複雜的沒有介面來傳遞的型別,使用Jackson的ObjectMapper來對映最終值

Querydsl Web支援【跳過】