查詢方法
本節提供了一些關於 Spring Data JDBC 實現和使用的具體資訊。
您通常在儲存庫上觸發的大多數資料訪問操作都會導致針對資料庫執行查詢。定義此類查詢只需在儲存庫介面上宣告一個方法,如以下示例所示
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 的人員。查詢是透過解析方法名稱中可以與 And 和 Or 連線的約束來派生的。因此,方法名稱產生了一個查詢表示式 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 用於解析當前使用者的使用者名稱。 |
下表顯示了查詢方法支援的關鍵字
| 關鍵字 | 示例 | 邏輯結果 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
字串上的 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
查詢派生僅限於可以在 WHERE 子句中不使用聯接的屬性。 |
查詢查詢策略
JDBC 模組支援在 @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 陣列型別的原始型別陣列。將為對映、列表、集合和實體陣列生成單獨的查詢。
一對一關係的屬性名稱必須以關係名稱加 _ 為字首。例如,如果上述示例中的 User 具有一個包含屬性 city 的 address,則該 city 的列必須標記為 address_city。
請注意,基於字串的查詢不支援分頁,也不接受 Sort、PageRequest 和 Limit 作為查詢引數,因為對於這些查詢,需要重寫查詢。如果您想應用限制,請使用 SQL 表達此意圖並自行將相應的引數繫結到查詢。 |
查詢可能包含 SpEL 表示式。有兩種變體,它們的評估方式不同。
在第一種變體中,SpEL 表示式以 : 為字首,並像繫結變數一樣使用。這樣的 SpEL 表示式將被替換為繫結變數,並且該變數將繫結到 SpEL 表示式的結果。
@Query("SELECT * FROM person WHERE id = :#{#person.id}")
Person findWithSpEL(PersonRef person);
這可以用於訪問引數的成員,如上述示例所示。對於更復雜的用例,可以在應用程式上下文中提供 EvaluationContextExtension,這反過來可以使任何物件在 SpEL 中可用。
另一種變體可以在查詢的任何位置使用,並且評估查詢的結果將替換查詢字串中的表示式。
@Query("SELECT * FROM #{tableName} WHERE id = :id")
Person findWithSpEL(PersonRef person);
它在第一次執行之前評估一次,並使用添加了兩個變數 tableName 和 qualifiedTableName 的 StandardEvaluationContext。當表名本身是動態的,因為它們也使用 SpEL 表示式時,這種用法最有用。
Spring 完全支援基於 -parameters 編譯器標誌的 Java 8 引數名稱發現。透過在構建中使用此標誌作為除錯資訊的替代,您可以省略命名引數的 @Param 註解。 |
| Spring Data JDBC 僅支援命名引數。 |
命名查詢
如果如上一節所述的註解中未提供查詢,Spring Data JDBC 將嘗試定位命名查詢。有兩種方式可以確定查詢的名稱。預設方式是獲取查詢的領域類,即儲存庫的聚合根,獲取其簡單名稱,並追加用 . 分隔的方法名稱。另外,@Query 註解有一個 name 屬性,可用於指定要查詢的查詢名稱。
命名查詢預計會在 classpath 上的屬性檔案 META-INF/jdbc-named-queries.properties 中提供。
可以透過設定 @EnableJdbcRepositories.namedQueriesLocation 的值來更改該檔案的位置。
命名查詢的處理方式與透過註解提供的查詢相同。
流式結果
當您將 Stream 指定為查詢方法的返回型別時,Spring Data JDBC 會在元素可用時立即返回它們。在處理大量資料時,這適用於降低延遲和記憶體需求。
流包含一個到資料庫的開放連線。為避免記憶體洩漏,該連線最終需要透過關閉流來關閉。推薦的做法是使用 try-with-resource 語句。這也意味著,一旦到資料庫的連線關閉,流將無法獲取更多元素,並可能丟擲異常。
自定義 RowMapper 或 ResultSetExtractor
@Query 註解允許您指定要使用的自定義 RowMapper 或 ResultSetExtractor。屬性 rowMapperClass 和 resultSetExtractorClass 允許您指定要使用的類,這些類將使用預設建構函式例項化。或者,您可以將 rowMapperClassRef 或 resultSetExtractorClassRef 設定為 Spring 應用程式上下文中的 bean 名稱。
如果您不想僅為單個方法使用特定的 RowMapper,而是為所有具有返回特定型別的自定義查詢的方法使用,您可以註冊一個 RowMapperMap bean 併為每個方法返回型別註冊一個 RowMapper。以下示例演示瞭如何註冊 DefaultQueryMappingConfiguration
@Bean
QueryMappingConfiguration rowMappers() {
return new DefaultQueryMappingConfiguration()
.register(Person.class, new PersonRowMapper())
.register(Address.class, new AddressRowMapper());
}
在確定要用於方法的 RowMapper 時,根據方法的返回型別,遵循以下步驟
-
如果型別是簡單型別,則不使用
RowMapper。相反,查詢預計會返回具有單個列的單行,並將轉換應用於該值以匹配返回型別。
-
QueryMappingConfiguration中的實體類會被迭代,直到找到一個作為所討論返回型別的超類或介面的實體類。將使用為該類註冊的RowMapper。迭代按註冊順序進行,因此請務必在特定型別之後註冊更通用型別。
如果適用,包裝器型別(例如集合或 Optional)將被解包。因此,Optional<Person> 的返回型別在上述過程中使用 Person 型別。
透過 QueryMappingConfiguration、@Query(rowMapperClass=…) 使用自定義 RowMapper,或使用自定義 ResultSetExtractor 會停用實體回撥和生命週期事件,因為結果對映可以在需要時發出自己的事件/回撥。 |