查詢方法

您通常在 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 該方法展示了根據給定的 firstname 查詢所有人員,一旦 firstname 由給定的 Publisher 發出。
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

findByFirstnameNotNull()

firstname IS NOT NULL

IsNull, 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 Repositories 的自定義實現中描述的功能來新增自定義修改行為。

使用 @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);
}
請注意,基於 String 的查詢不支援分頁,也不接受 SortPageRequestLimit 作為查詢引數,因為對於這些查詢,需要重寫查詢。如果您想應用限制,請使用 SQL 表達此意圖並自行將適當的引數繫結到查詢。
Spring 完全支援基於 -parameters 編譯器標誌的 Java 8 引數名稱發現。在您的構建中使用此標誌作為除錯資訊的替代方案,可以省略命名引數的 @Param 註解。

包含 SpEL 表示式的查詢

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

SpEL 表示式可以提供斷言值 (predicate values),這些值在執行查詢之前進行評估。

表示式透過一個包含所有引數的陣列暴露方法引數。以下查詢使用 [0] 來宣告 lastname 的斷言值(等同於 :lastname 引數繫結)。

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

這種表示式支援可以透過查詢 SPI(org.springframework.data.spel.spi.EvaluationContextExtension)進行擴充套件。查詢 SPI 可以貢獻屬性和函式,並可以自定義根物件。擴充套件在構建查詢時,在 SpEL 評估時從應用上下文中檢索。

將 SpEL 表示式與普通引數結合使用時,請使用命名引數表示法而非原生繫結標記,以確保正確的繫結順序。

使用表示式的另一種方法是在查詢中間使用,與引數無關。評估表示式的結果將替換查詢字串中的表示式。

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

它在第一次執行前評估一次,並使用一個添加了 tableNamequalifiedTableName 兩個變數的 StandardEvaluationContext。當表名本身是動態的(因為它們也使用 SpEL 表示式)時,這種用法最有用。

在查詢字串中使用 SpEL 是增強查詢的強大方法。但是,它們也可能接受各種不必要的引數。在將字串傳遞給查詢之前,您應該確保對其進行淨化(sanitize),以避免對查詢造成不必要的更改。