查詢方法

您通常在 repository 上觸發的大部分資料存取操作,都會導致針對資料庫執行查詢。定義此類查詢就是在 repository 介面上宣告方法,如下例所示

範例 1. 具有查詢方法的 PersonRepository
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {

  Flux<Person> findByFirstname(String firstname);                                   (1)

  Flux<Person> findByFirstname(Publisher<String> firstname);                        (2)

  Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); (3)

  Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);       (4)

  Mono<Person> findFirstByLastname(String lastname);                                (5)

  @Query("SELECT * FROM person WHERE lastname = :lastname")
  Flux<Person> findByLastname(String lastname);                                     (6)

  @Query("SELECT firstname, lastname FROM person WHERE lastname = $1")
  Mono<Person> findFirstByLastname(String lastname);                                (7)
}
1 此方法顯示查詢所有具有給定 firstname 的人員。此查詢是透過解析方法名稱以取得可與 AndOr 串連的條件約束而衍生。因此,方法名稱會產生 SELECT … FROM person WHERE firstname = :firstname 的查詢表示式。
2 此方法顯示在給定的 Publisher 發出 firstname 後,查詢所有具有給定 firstname 的人員。
3 使用 Pageable 將偏移量和排序參數傳遞至資料庫。
4 尋找符合給定條件的單一實體。在非唯一結果上,它會以 IncorrectResultSizeDataAccessException 完成。
5 除非 <4>,否則即使查詢產生更多結果列,也始終會發出第一個實體。
6 findByLastname 方法顯示查詢所有具有給定姓氏的人員。
7 針對單一 Person 實體的查詢,僅投影 firstnamelastname 欄位。帶註解的查詢使用原生繫結標記,在此範例中為 Postgres 繫結標記。

請注意,@Query 註解中使用的 select 陳述式的欄位必須符合 NamingStrategy 為各自屬性產生的名稱。如果 select 陳述式不包含相符的欄位,則不會設定該屬性。如果持久化建構子需要該屬性,則會提供 null 或(對於基本類型)預設值。

下表顯示查詢方法支援的關鍵字

表 1. 查詢方法支援的關鍵字
關鍵字 範例 邏輯結果

After (之後)

findByBirthdateAfter(Date date)

birthdate > date

GreaterThan (大於)

findByAgeGreaterThan(int age)

age > age

GreaterThanEqual (大於等於)

findByAgeGreaterThanEqual(int age)

age >= age

Before (之前)

findByBirthdateBefore(Date date)

birthdate < date

LessThan (小於)

findByAgeLessThan(int age)

age < age

LessThanEqual (小於等於)

findByAgeLessThanEqual(int age)

age <= age

Between (介於)

findByAgeBetween(int from, int to)

age BETWEEN from AND to

NotBetween (不介於)

findByAgeNotBetween(int from, int to)

age NOT BETWEEN from AND to

In (在...之中)

findByAgeIn(Collection<Integer> ages)

age IN (age1, age2, ageN)

NotIn (不在...之中)

findByAgeNotIn(Collection ages)

age NOT IN (age1, age2, ageN)

IsNotNull, NotNull (非 Null)

findByFirstnameNotNull()

firstname IS NOT NULL

IsNull, Null (為 Null)

findByFirstnameNull()

firstname IS NULL

Like, StartingWith, EndingWith (相似、開始於、結束於)

findByFirstnameLike(String name)

firstname LIKE name

NotLike, IsNotLike (不相似)

findByFirstnameNotLike(String name)

firstname NOT LIKE name

Containing on String (字串包含)

findByFirstnameContaining(String name)

firstname LIKE '%' + name +'%'

NotContaining on String (字串不包含)

findByFirstnameNotContaining(String name)

firstname NOT LIKE '%' + name +'%'

(無關鍵字)

findByFirstname(String name)

firstname = name

Not (非)

findByFirstnameNot(String name)

firstname != name

IsTrue, True (為真)

findByActiveIsTrue()

active IS TRUE

IsFalse, False (為假)

findByActiveIsFalse()

active IS FALSE

修改查詢

先前的章節說明如何宣告查詢以存取給定的實體或實體集合。先前表格中的關鍵字可以與 delete…Byremove…By 結合使用,以建立衍生查詢,以刪除相符的列。

範例 2. Delete…By 查詢
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {

  Mono<Integer> deleteByLastname(String lastname);            (1)

  Mono<Void> deletePersonByLastname(String lastname);         (2)

  Mono<Boolean> deletePersonByLastname(String lastname);      (3)
}
1 使用 Mono<Integer> 的回傳類型會傳回受影響的列數。
2 使用 Void 僅報告列是否已成功刪除,而不會發出結果值。
3 使用 Boolean 報告是否已移除至少一列。

由於此方法適用於全面的自訂功能,因此您可以修改只需要參數繫結的查詢,方法是使用 @Modifying 註解查詢方法,如下例所示

@Modifying
@Query("UPDATE person SET firstname = :firstname where lastname = :lastname")
Mono<Integer> setFixedFirstnameFor(String firstname, String lastname);

修改查詢的結果可以是

  • Void (或 Kotlin Unit) 以捨棄更新計數並等待完成。

  • Integer 或其他數值類型,發出受影響的列數。

  • Boolean,以發出是否已更新至少一列。

@Modifying 註解僅與 @Query 註解結合使用時才相關。衍生的自訂方法不需要此註解。

修改查詢會直接針對資料庫執行。不會呼叫任何事件或回呼。因此,如果稽核註解的欄位未在帶註解的查詢中更新,則它們也不會更新。

或者,您可以使用 Spring Data Repository 的自訂實作 中描述的功能來新增自訂修改行為。

使用 @Query

以下範例示範如何使用 @Query 宣告查詢方法

透過使用 @Query 宣告查詢方法
interface UserRepository extends ReactiveCrudRepository<User, Long> {

  @Query("select firstName, lastName from User u where u.emailAddress = :email")
  Flux<User> findByEmailAddress(@Param("email") String email);
}
請注意,基於字串的查詢不支援分頁,也不接受 SortPageRequestLimit 作為查詢參數,因為對於這些查詢,將需要重寫查詢。如果您想要套用限制,請使用 SQL 表達此意圖,並自行將適當的參數繫結至查詢。
Spring 完全支援基於 Java 8 的 -parameters 編譯器旗標的參數名稱探索。透過在您的建置中使用此旗標作為偵錯資訊的替代方案,您可以省略具名參數的 @Param 註解。

使用 SpEL 表示式的查詢

查詢字串定義可以與 SpEL 表示式一起使用,以在執行階段建立動態查詢。SpEL 表示式可以透過兩種方式使用。

SpEL 表示式可以提供述詞值,這些值會在執行查詢之前立即評估。

表示式透過包含所有引數的陣列公開方法引數。以下查詢使用 [0] 來宣告 lastname 的述詞值(相當於 :lastname 參數繫結)

@Query("SELECT * FROM person WHERE lastname = :#{[0]}")
Flux<Person> findByQueryWithParameterExpression(String lastname);

此表示式支援可透過 Query SPI 擴充:org.springframework.data.spel.spi.EvaluationContextExtension。Query SPI 可以貢獻屬性和函數,並且可以自訂根物件。擴充功能會在建置查詢時 SpEL 評估時從應用程式內容中擷取。

將 SpEL 表示式與純參數結合使用時,請使用具名參數標記法,而不是原生繫結標記,以確保正確的繫結順序。

使用表示式的另一種方式是在查詢的中間,獨立於參數。評估查詢的結果將取代查詢字串中的表示式。

在查詢中使用 SpEL
@Query("SELECT * FROM #{tableName} WHERE lastname = :lastname")
Flux<Person> findByQueryWithExpression(String lastname);

它會在第一次執行之前評估一次,並使用新增了兩個變數 tableNamequalifiedTableNameStandardEvaluationContext。當表名稱本身是動態的時,此用法最有用,因為它們也使用 SpEL 表示式。

查詢字串中的 SpEL 可以是增強查詢的強大方法。但是,它們也可能接受廣泛的不必要引數。您應確保在將字串傳遞至查詢之前對其進行清理,以避免對查詢進行不必要的變更。