查詢方法

本節提供了一些關於 Spring Data JDBC 的實現和使用的具體資訊。

大部分你在 Repository 上觸發的資料訪問操作都會導致資料庫查詢。定義這樣的查詢可以透過在 Repository 介面上宣告一個方法來實現,如下例所示

帶有查詢方法的 PersonRepository
interface PersonRepository extends PagingAndSortingRepository<Person, String> {

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

  List<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); (2)

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

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

  Person findByFirstnameAndLastname(String firstname, String lastname);             (5)

  Person findFirstByLastname(String lastname);                                      (6)

  @Query("SELECT * FROM person WHERE lastname = :lastname")
  List<Person> findByLastname(String lastname);                                     (7)
  @Query("SELECT * FROM person WHERE lastname = :lastname")
  Stream<Person> streamByLastname(String lastname);                                     (8)

  @Query("SELECT * FROM person WHERE username = :#{ principal?.username }")
  Person findActiveUser();															(9)
}
1 該方法展示了查詢所有具有給定 firstname 的人的查詢。該查詢是透過解析方法名來派生約束條件的,這些約束條件可以透過 AndOr 進行連線。因此,方法名會生成一個查詢表示式,如 SELECT … FROM person WHERE firstname = :firstname
2 使用 Pageable 向資料庫傳遞偏移量和排序引數。
3 返回一個 Slice<Person>。選擇 LIMIT+1 行以確定是否有更多資料可供消費。不支援 ResultSetExtractor 自定義。
4 執行一個分頁查詢,返回 Page<Person>。僅選擇給定頁面範圍內的 資料,並可能執行計數查詢以確定總數。不支援 ResultSetExtractor 自定義。
5 根據給定條件查詢單個實體。對於非唯一結果,會丟擲 IncorrectResultSizeDataAccessException 異常。
6 與 <3> 不同,即使查詢產生了更多結果文件,也總是會發出第一個實體。
7 findByLastname 方法展示了查詢所有具有給定 lastname 的人的查詢。
8 streamByLastname 方法返回一個 Stream,這使得值一旦從資料庫返回就可以立即使用。
9 你可以使用 Spring Expression Language 動態解析引數。在示例中,使用 Spring Security 解析當前使用者的使用者名稱。

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

表 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 用於 String

findByFirstnameContaining(String name)

firstname LIKE '%' + name + '%'

NotContaining 用於 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

查詢派生僅限於可以在 WHERE 子句中使用且無需連線的屬性。

查詢查詢策略

JDBC 模組支援在 @Query 註解中將查詢手動定義為字串,或在屬性檔案中將其定義為命名查詢。

當前,從方法名稱派生查詢僅限於簡單屬性,即直接存在於聚合根中的屬性。此外,這種方法僅支援 select 查詢。

使用 @Query

下例展示瞭如何使用 @Query 宣告一個查詢方法

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

  @Query("select firstName, lastName from User u where u.emailAddress = :email")
  User findByEmailAddress(@Param("email") String email);
}

預設情況下,將查詢結果轉換為實體時,使用的 RowMapper 與 Spring Data JDBC 自身生成的查詢所使用的相同。你提供的查詢必須與 RowMapper 期望的格式匹配。必須提供實體建構函式中使用的所有屬性的列。透過 setter、wither 或欄位訪問設定的屬性的列是可選的。結果中沒有匹配列的屬性將不會被設定。該查詢用於填充聚合根、嵌入式實體以及一對一關係,包括原始型別陣列(這些陣列儲存和載入為 SQL 陣列型別)。對於 maps、lists、sets 和實體陣列,會生成單獨的查詢。

一對一關係中的屬性名稱必須以關係名稱加上 _ 為字首。例如,如果上面示例中的 User 有一個帶 city 屬性的 address,則該 city 的列必須命名為 address_city

請注意,基於字串的查詢不支援分頁,也不接受 SortPageRequestLimit 作為查詢引數,因為這些查詢需要重寫。如果你想應用限制,請使用 SQL 表達此意圖,並自行將適當的引數繫結到查詢中。

查詢可以包含 SpEL 表示式。有兩種評估方式不同的變體。

第一種變體中,SpEL 表示式以 : 為字首,並像繫結變數一樣使用。這樣的 SpEL 表示式將被替換為一個繫結變數,並且該變數將繫結到 SpEL 表示式的結果。

在查詢中使用 SpEL
@Query("SELECT * FROM person WHERE id = :#{#person.id}")
Person findWithSpEL(PersonRef person);

這可以用於訪問引數的成員,如上面的示例所示。對於更復雜的用例,可以在應用程式上下文中提供一個 EvaluationContextExtension,它反過來可以使任何物件在 SpEL 中可用。

另一種變體可以在查詢中的任何位置使用,評估查詢的結果將替換查詢字串中的表示式。

在查詢中使用 SpEL
@Query("SELECT * FROM #{tableName} WHERE id = :id")
Person findWithSpEL(PersonRef person);

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

Spring 完全支援基於 -parameters 編譯器標誌的 Java 8 引數名發現。在構建中使用此標誌作為除錯資訊的替代方案,可以省略命名引數的 @Param 註解。
Spring Data JDBC 僅支援命名引數。

命名查詢

如果在註解中沒有提供查詢(如前一節所述),Spring Data JDBC 將嘗試查詢一個命名查詢。確定查詢名稱有兩種方式。預設方式是取查詢的領域類,即 Repository 的聚合根,取其簡單名稱,然後附加方法名稱,用 . 分隔。或者,@Query 註解有一個 name 屬性,可用於指定要查詢的查詢名稱。

命名查詢預計在類路徑下的屬性檔案 META-INF/jdbc-named-queries.properties 中提供。

可以透過設定 @EnableJdbcRepositories.namedQueriesLocation 的值來更改該檔案的位置。

命名查詢的處理方式與註解提供的查詢相同。

自定義查詢方法

流式結果

當你將 Stream 指定為查詢方法的返回型別時,Spring Data JDBC 會在元素可用時立即返回它們。處理大量資料時,這適用於減少延遲和記憶體需求。

流中包含一個到資料庫的開啟連線。為了避免記憶體洩漏,最終需要透過關閉流來關閉該連線。推薦的方法是使用 try-with-resource 子句。這也意味著,一旦到資料庫的連線關閉,流將無法獲取更多元素,並可能丟擲異常。

自定義 RowMapperResultSetExtractor

@Query 註解允許你指定要使用的自定義 RowMapperResultSetExtractor。屬性 rowMapperClassresultSetExtractorClass 允許你指定要使用的類,這些類將使用預設建構函式例項化。或者,你可以將 rowMapperClassRefresultSetExtractorClassRef 設定為你 Spring 應用程式上下文中的一個 bean 名稱。

如果你不僅想為一個方法使用特定的 RowMapper,而是為所有返回特定型別的自定義查詢方法使用它,你可以註冊一個 RowMapperMap bean,併為每種方法返回型別註冊一個 RowMapper。下例展示瞭如何註冊 DefaultQueryMappingConfiguration

@Bean
QueryMappingConfiguration rowMappers() {
  return new DefaultQueryMappingConfiguration()
    .register(Person.class, new PersonRowMapper())
    .register(Address.class, new AddressRowMapper());
}

根據方法的返回型別,在確定為方法使用哪個 RowMapper 時,遵循以下步驟

  1. 如果型別是簡單型別,則不使用 RowMapper

    相反,查詢預計返回只有一列的單行,並且會對此值應用到返回型別的轉換。

  2. 迭代 QueryMappingConfiguration 中的實體類,直到找到一個作為目標返回型別的超類或介面的類。使用為該類註冊的 RowMapper

    迭代按註冊順序進行,因此請確保在註冊特定型別之後註冊更通用的型別。

如果適用,包裝器型別(如集合或 Optional)將被解包。因此,返回型別 Optional<Person> 在前面的過程中使用 Person 型別。

透過 QueryMappingConfiguration@Query(rowMapperClass=…) 或自定義 ResultSetExtractor 使用自定義 RowMapper 會停用 Entity Callbacks 和 Lifecycle Events,因為結果對映可以在需要時觸發自己的事件/回撥。

修改查詢

你可以使用查詢方法上的 @Modifying 註解將查詢標記為修改查詢,如下例所示

@Modifying
@Query("UPDATE DUMMYENTITY SET name = :name WHERE id = :id")
boolean updateName(@Param("id") Long id, @Param("name") String name);

你可以指定以下返回型別

  • void

  • int (更新記錄數)

  • boolean (記錄是否已更新)

修改查詢直接針對資料庫執行。不會呼叫任何事件或回撥。因此,即使帶有審計註解的欄位在註解查詢中未更新,它們也不會被更新。