Spring JPA 定義查詢方法

Spring JPA 定義查詢方法

翻譯:Defining Query Methodshtml

​ 存儲庫代理有兩種方式基於方法名派生特定域的查詢方式:java

  • 直接從方法名派生查詢
  • 自定義查詢方式

​ 可用選項基於實際存儲。可是,必須有一個策略來決定建立什麼樣的實際查詢。下一節將介紹可用的選項。算法

一、查詢查找策略

​ 如下策略可用於存儲庫基礎結構來解決查詢。使用XML配置,能夠經過querylookup strategy屬性在名稱空間配置策略。對於Java配置,可使用Enable${store}Repositories註釋的queryLookupStrategy屬性。但某些策略可能不支持特定的數據存儲。spring

  • create查詢方式嘗試從查詢方法名稱構造特定於存儲的查詢。通常是刪除從方法中刪除不用的部分,而後細化用到的部分。你能夠從Query-Creation瞭解更多關於查詢建立的內容。
  • USE_DECLARED_QUERY嘗試查找已聲明的查詢,若是找不到則引起異常。查詢能夠經過某個地方的註釋進行定義,或經過其餘方式進行聲明。請參閱特定存儲庫方法的文檔,以找到該存儲庫內的可用方法。若是存儲庫基礎結構在引導時未找到方法的聲明查詢,則致使失敗。
  • CREATE_IF_NOT_FOUND(默認)結合CREATEUSE_DECLARED_QUERY的查詢。它首先查找已聲明的查詢,若是沒有找到聲明的查詢,它將建立一個基於自定義方法名的查詢。這是默認的查找策略,所以,若是未顯式配置任何內容,則使用此策略。它容許經過方法名快速定義查詢,還能夠根據須要引入聲明的查詢來定製這些查詢。

二、查詢建立

​ Spring數據存儲庫基礎方法中內置的查詢生成器機制對於在存儲庫的實體上構建的約束查詢很是有用。該機制從方法中剝離前綴find…By、read…By、query…By、count…Byget…By,並開始解析其他部分。引入子句能夠包含更多的表達式,例如在要建立的查詢上設置Distinct標誌的Distinct。第一個By充當分隔符,指示實際條件的開始。您能夠定義實體屬性的條件,並將它們使用andOr鏈接起來。如下示例演示如何建立多個查詢:編程

例13:從方法名建立查詢api

interface PersonRepository extends Repository<Person, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // 容許去重查詢
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // 容許忽略大小寫查詢
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // 容許查詢結果進行排序
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

解析方法的實際結果取決因而基於哪一個持久類實體進行的查詢建立,可是,也有一些通常性問題須要注意:安全

  • 表達式一般是屬性字段和運算符組合在一塊兒進行遍歷,你可使用AND或者OR組合屬性表達式,同時也支持Between, LessThan, GreaterThan, 和Like等運算符,支持的運算符可能因數據存儲而異,具體請參考文檔的相應部分。
  • 方法解析器支持爲單個屬性(例如findByLastnameIgnoreCase(…))或支持忽略大小寫的類型的全部屬性設置IgnoreCase標誌(一般是字符串實例  ,例如findByLastnameAndFirstnameAllIgnoreCase(…))。是否支持忽略大小寫可能因存儲而異,所以請參閱參考文檔中的相關部分以瞭解特定於存儲的查詢方法。
  • 經過向引用屬性的查詢方法追加OrderBy子句並提供排序方向(AscDesc),能夠應用靜態排序。要建立支持動態排序的查詢方法,請參閱「特殊參數處理」。

三、屬性表達式

​ 屬性表達式只能引用實體類定義的直接屬性,如上例所示,在建立查詢時,你已經肯定屬性是實體類對應域中的屬性,除此以外,還能夠經過嵌套屬性定義約束。性能優化

List<Person> findByAddressZipCode(ZipCode zipCode);

​ 假定一我的擁有一個帶郵政編碼的地址,在這種狀況下,該方法遍歷建立屬性x.address.zipCode. 解析算法首先將整個部分(AddressZipCode)解釋爲屬性,而後在域類中檢查具備該名稱(未大寫)的屬性。若是算法成功,則使用該屬性。若是不是這樣,算法會把駝峯命名部分的源代碼拆分,並嘗試在咱們的示例中找到相應的屬性 AddressZipCode。若是算法找到一個帶有該頭部的屬性,它將獲取尾部並繼續從那裏構建樹,並按照剛纔描述的方式將尾部拆分。若是第一個拆分不匹配,則算法將拆分點向左移動(Address、ZipCode)並繼續。dom

舉例說明拆分:AaBbCc異步

第一次拆分 AaBb / Cc 獲取屬性方式 AaBb.Cc

第二次拆分 Aa / BbCc 獲取屬性方式 Aa.BbCc

​ 儘管這在大多數狀況下都是可行的,但算法仍然可能會選擇錯誤的屬性。假設Person類也有一個addressZip屬性。該算法已經在第一輪分割中匹配,選擇了錯誤的屬性,而後就會失敗(由於addressZip的類型可能沒有代碼屬性)。

​ 要解決這種歧義,能夠在方法名內部手動定義遍歷點(以 - 定義遍歷點)。

List<Person> findByAddress_ZipCode(ZipCode zipCode);

由於咱們將下劃線字符視爲保留字符,因此咱們強烈建議遵循標準的Java命名約定(即在屬性名稱中不使用下劃線,而是使用駝峯大小寫)。

四、特殊參數處理

​ 要處理查詢中的參數,請像前面示例中所看到的那樣定義方法參數。除此以外,基礎結構還識別某些特定類型,如分頁和排序,動態地對查詢應用分頁和排序。下面的示例演示了這些特性。

例14:在查詢中使用Pageable, Slice, 和 Sort

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

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

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

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

採用排序和可分頁的api但願將非空值傳遞給方法。若是不想應用任何排序或分頁,可使用Sort.unsorted()和Pageable.unpaged()。

​ 第一個方法容許您傳遞一個org.springframework.data.domain查詢方法的分頁實例,以動態地向靜態定義的查詢添加分頁。Page獲取到了可用元素和頁面的總數。它是經過基礎結構觸發計數查詢來計算總數量來實現的。由於這可能會廢算力(取決於所使用的存儲),因此能夠返回一個Slice。一個片只知道下一個片是否可用,這在遍歷更大的結果集時可能就足夠了。

TIPS:出於性能優化考慮,建議使用Slice

​ 排序一樣經過Pageable實例進行處理,若是你只須要進行排序,只須要在你的方法中添加一個org.springframework.data.domain.Sort參數。如您所見,返回列表也是可能的。 在這種狀況下,將不會建立構建Page實例所需的其餘元數據(這意味着沒有發出必要的附加計數查詢)。相反,它將查詢限制爲僅查找給定範圍的實體。

要查明整個查詢獲得了多少頁,必須觸發一個額外的count查詢。默認狀況下,該查詢派生自您實際觸發的查詢。

可使用屬性名定義簡單的排序表達式。能夠將表達式鏈接起來,將多個表達式整合到一個表達式中。

例15:定義查詢表達式

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

要以更類型安全的方式定義排序表達式,請從定義用於的排序表達式的類型開始,並使用方法引用定義要排序的屬性

例16:使用類型安全的API定義排序表達式

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

Sort sort = person.by(Person::getFirstname).ascending()
  .and(person.by(Person::getLastname).descending());

TypedSort.by(…)一般經過使用CGlib來做爲運行時代理,當使用Graal VM Native等工具時,CGlib可能會干擾本機映像的編譯。

若是您的存儲實現支持Querydsl,您還可使用生成的元模型類型來定義排序表達式。

例17: 使用Querydsl API定義排序表達式

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

五、查詢結果限制

查詢結果可使用互換使用的top或者first關鍵字來進行限制,能夠將一個可變的數字值附加到topfirst,以指定返回的最大結果大小。若是遺漏了這個數字,則使用默認值1。下面的示例顯示如何限制查詢大小。

例18:使用topfirst限制查詢返回結果的大小

User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

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

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

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

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

限制表達式還支持Distinct關鍵字。另外,對於將結果集限制爲一個實例的查詢,支持使用Optional關鍵字包裝結果。

若是將分頁或切片應用於限制查詢分頁(以及計算可用頁面數量),則將其應用於有限的結果。

經過使用Sort參數來限制結果與動態排序的組合,能夠表達最小和最大元素的查詢方法。

六、返回集合或迭代的存儲庫方法

​ 返回多個結果的查詢方法可使用標準的Java Iterable, List, Set。除此以外,咱們還支持返回Spring數據的Streamable, Iterable的自定義擴展,以及Vavr提供的集合類型。

例19:使用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"));

返回自定義可以使用Streamable包裝的類型

​ 爲集合提供專用的包裝器類型是一種經常使用的模式,用於爲返回多個元素的查詢執行結果提供API。一般經過調用存儲庫方法返回類集合類型並手動建立包裝器類型的實例來使用這些類型。能夠避免這個額外的步驟,由於Spring Data容許使用這些包裝器類型做爲查詢方法返回類型,若是它們知足如下標準:

  1. 該類型繼承實現了Streamable
  2. 該類型公開名爲of()valueOf()的構造函數或靜態工廠方法,以Streamable做爲參數。

用例以下所示:

class Product { 
    //產品實體公開訪問價格的API
  MonetaryAmount getPrice() { … }
}

@RequiredArgConstructor(staticName = "of")
class Products implements Streamable<Product> { 
//可經過產品構造的Streamable<Product>的包裝器類型。of()(經過Lombok註釋建立的工廠方法)。
  private Streamable<Product> streamable;

  public MonetaryAmount getTotal() { 
    return streamable.stream() //包裝器類型公開了在Streamable<Product>上計算新值的附加API。
      .map(Priced::getPrice)
      .reduce(Money.of(0), MonetaryAmount::add);
  }
}

interface ProductRepository implements Repository<Product, Long> {
    //包裝器類型能夠直接用做查詢方法返回類型。不須要返回Stremable<Product>並手動將其封裝到存儲庫客戶機中。
  Products findAllByDescriptionContaining(String text); 
}

Vavr 集合的支持

Vavr是一個包含Java中函數式編程概念的庫。它附帶了一組可用做查詢方法返回類型的自定義集合類型。

Vavr 集合類型 Vavr 實現的類型 Valid Java 源類型
io.vavr.collection.Seq io.vavr.collection.List java.util.Iterable
io.vavr.collection.Set io.vavr.collection.LinkedHashSet java.util.Iterable
io.vavr.collection.Map io.vavr.collection.LinkedHashMap java.util.Map

​ 第一列中的類型(或其子類型)能夠用做查詢方法返回類型,並將根據實際查詢結果的Java類型(第三列)得到做爲實現類型的第二列中的類型。而後經過實現派生類的方法進行類型轉化。

七、空值方法處理庫

​ 在Spring Data 2.0中,返回單個聚合實例的存儲庫CRUD方法使用Java 8 s可選來指示可能缺乏的值。除此以外,Spring Data還支持在查詢方法上返回如下包裝器類型:

  • com.google.common.base.Optional
  • scala.Option
  • io.vavr.control.Option

​ 或者,查詢方法能夠選擇根本不使用包裝器類型。若是沒有查詢結果,則返回null。返回集合、集合替代、包裝器和流的存儲庫方法保證不會返回null,而是返回相應的空表示。有關詳細信息,請參見存儲庫查詢返回類型。

空值註解

您可使用Spring Framework的可空性註釋來表示存儲庫方法的可空性約束。它們提供了一種工具友好的方法,並在運行時選擇空檢查,以下所示:

  • @NonNullApi:在包級別上使用,用於聲明參數和返回值的默認行爲是不接受或生成空值。
  • @NonNull:用於不能爲null的參數或返回值(在@NonNullApi應用的地方,參數和返回值不須要)。
  • @Nullable:用於能夠爲空的參數或返回值。

Spring註釋使用JSR 305註釋(一種中止維護但普遍傳播的JSR)進行元註釋。JSR 305元註釋讓工具供應商(如IDEA、Eclipse和Kotlin)以通用的方式提供空安全支持,而沒必要對Spring註釋進行硬編碼支持。要啓用查詢方法的nullability約束的運行時檢查,您須要在package-info中使用Spring 的@NonNullApi來激活package-info.java上的非空配置,以下面的示例所示

例20:在包級別上聲明非空

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

​ 一旦設置了非空默認值,存儲庫查詢方法調用將在運行時驗證是否存在可空性約束。若是查詢執行結果違反定義的約束,則拋出異常。當方法將返回null,但聲明爲不可空時(存儲庫所在的包上定義的註釋的默認值),就會發生這種狀況。若是您但願再次選擇可爲空的結果,能夠在單個方法上有選擇地使用@Nullable。使用本節開始提到的結果包裝器類型繼續按預期工做:空結果被轉換爲表示缺席的值。下面的示例顯示了剛纔描述的許多技術:

例21:使用不一樣的空值配置

package com.acme;                                 // 存儲庫駐留在一個包(或子包)中,咱們爲其定義了非空行爲。                     

import org.springframework.lang.Nullable;

interface UserRepository extends Repository<User, Long> {

  User getByEmailAddress(EmailAddress emailAddress);      //當執行的查詢不產生結果時,拋出EmptyResultDataAccessException。當傳遞給方法的電子郵件地址爲空時,拋出IllegalArgumentException異常。              

  @Nullable
  User findByEmailAddress(@Nullable EmailAddress emailAdress);       //當執行的查詢沒有產生結果時,返回null。還接受null做爲emailAddress的值。   

  Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); //當執行的查詢不產生結果時,返回Optional.empty()。當傳遞給方法的電子郵件地址爲空時,拋出IllegalArgumentException異常。
}

八、Stream化查詢結果

​ 經過使用Java 8 Stream 做爲返回類型,能夠漸進地處理查詢方法的結果。與將查詢結果包裝在流數據存儲中不一樣,使用特定的方法執行流,以下面的示例所示

例23:用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);

流可能包裝了底層數據存儲特定的資源,所以在使用後必須關閉。您可使用close()方法手動關閉流,也可使用Java 7 try-with-resources塊,以下面的示例所示

例24:try-catch形式使用Stream

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

9 、異步查詢結果

​ 經過使用Spring的異步方法執行能力,存儲庫查詢能夠異步運行。這意味着,當實際的查詢執行發生在已提交給Spring TaskExecutor的任務中時,該方法在調用時當即返回。異步查詢執行與反應性查詢執行不一樣,不該該混合使用。有關響應性支持的更多細節,請參閱特定於存儲的文檔。下面的示例顯示了許多異步查詢

@Async
Future<User> findByFirstname(String firstname);               

@Async
CompletableFuture<User> findOneByFirstname(String firstname); 

@Async
ListenableFuture<User> findOneByLastname(String lastname);
相關文章
相關標籤/搜索