查詢方法
本節提供關於 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);
}
為了將查詢結果轉換為實體,預設會使用與 Spring Data JDBC 本身產生的查詢相同的 RowMapper
。您提供的查詢必須符合 RowMapper
期望的格式。必須提供實體建構子中使用的所有屬性的欄位。透過 setter、wither 或欄位存取設定的屬性欄位是選填的。結果中沒有相符欄位的屬性將不會被設定。查詢用於填充聚合根、嵌入式實體和一對一關係,包括以 SQL 陣列類型儲存和載入的基本類型陣列。針對 map、list、set 和實體陣列會產生個別的查詢。
一對一關係的屬性名稱必須以關係名稱加上 _
作為前綴。例如,如果上述範例中的 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);
它在第一次執行之前評估一次,並使用 StandardEvaluationContext
,其中新增了兩個變數 tableName
和 qualifiedTableName
。當表名稱本身是動態的時,這種用法最有用,因為它們也使用 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 會停用實體回呼和生命週期事件,因為結果對應可以在需要時發出自己的事件/回呼。 |