查詢方法
本節提供了一些關於 Spring Data JDBC 的實現和使用的具體資訊。
大部分你在 Repository 上觸發的資料訪問操作都會導致資料庫查詢。定義這樣的查詢可以透過在 Repository 介面上宣告一個方法來實現,如下例所示
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
註解中將查詢手動定義為字串,或在屬性檔案中將其定義為命名查詢。
當前,從方法名稱派生查詢僅限於簡單屬性,即直接存在於聚合根中的屬性。此外,這種方法僅支援 select 查詢。
使用 @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
。
請注意,基於字串的查詢不支援分頁,也不接受 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 將嘗試查詢一個命名查詢。確定查詢名稱有兩種方式。預設方式是取查詢的領域類,即 Repository 的聚合根,取其簡單名稱,然後附加方法名稱,用 .
分隔。或者,@Query
註解有一個 name
屬性,可用於指定要查詢的查詢名稱。
命名查詢預計在類路徑下的屬性檔案 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=…) 或自定義 ResultSetExtractor 使用自定義 RowMapper 會停用 Entity Callbacks 和 Lifecycle Events,因為結果對映可以在需要時觸發自己的事件/回撥。 |