查詢方法

本節提供關於 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 on String

findByFirstnameContaining(String name)

firstname LIKE '%' + name + '%'

NotContaining on String

findByFirstnameNotContaining(String name)

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

(No keyword)

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);
}

為了將查詢結果轉換為實體,預設會使用與 Spring Data JDBC 本身產生的查詢相同的 RowMapper。您提供的查詢必須符合 RowMapper 期望的格式。必須提供實體建構子中使用的所有屬性的欄位。透過 setter、wither 或欄位存取設定的屬性欄位是選填的。結果中沒有相符欄位的屬性將不會被設定。查詢用於填充聚合根、嵌入式實體和一對一關係,包括以 SQL 陣列類型儲存和載入的基本類型陣列。針對 map、list、set 和實體陣列會產生個別的查詢。

一對一關係的屬性名稱必須以關係名稱加上 _ 作為前綴。例如,如果上述範例中的 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);

它在第一次執行之前評估一次,並使用 StandardEvaluationContext,其中新增了兩個變數 tableNamequalifiedTableName。當表名稱本身是動態的時,這種用法最有用,因為它們也使用 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 註解允許您指定要使用的自訂 RowMapperResultSetExtractorrowMapperClassresultSetExtractorClass 屬性允許您指定要使用的類別,這些類別將使用預設建構子實例化。或者,您可以將 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 會停用實體回呼和生命週期事件,因為結果對應可以在需要時發出自己的事件/回呼。

修改查詢

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

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

您可以指定以下回傳類型

  • void

  • int (更新的記錄計數)

  • boolean (是否已更新記錄)

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