Spring Data JPA 中 findBy 和 findOneBy 的區別
1. 概述
Spring Data 存儲庫附帶了許多簡化數據訪問邏輯實現的方法。然而,選擇正確的方法並不總是像我們想像的那麼容易。
一個示例是以findBy
和findOneBy
為前綴的方法。儘管根據名稱它們似乎做相同的事情,但它們還是有點不同。
2. Spring Data中派生的查詢方法
Spring Data JPA 經常因其派生查詢方法功能而受到稱讚。這些方法提供了一種從方法名稱派生特定查詢的方法。例如,如果我們想通過foo
屬性檢索數據,我們可以簡單地編寫findByFoo()
。
通常,我們可以使用多個前綴來構造派生查詢方法。這些前綴包括findBy
和findOneBy
。那麼,讓我們看看它們的實踐。
3. 實際例子
首先,讓我們考慮一下Person
實體類:
@Entity
public class Person {
@Id
private int id;
private String firstName;
private String lastName;
// standard getters and setters
}
在這裡,我們將使用 H2 作為我們的數據庫。讓我們使用基本的 SQL 腳本為數據庫添加數據:
INSERT INTO person (id, first_name, last_name) VALUES(1, 'Azhrioun', 'Abderrahim');
INSERT INTO person (id, first_name, last_name) VALUES(2, 'Brian', 'Wheeler');
INSERT INTO person (id, first_name, last_name) VALUES(3, 'Stella', 'Anderson');
INSERT INTO person (id, first_name, last_name) VALUES(4, 'Stella', 'Wheeler');
最後,讓我們創建一個 JPA 存儲庫來管理我們的Person
實體:
@Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {
}
3.1.按前綴findBy
findBy
是創建表示搜索查詢的派生查詢方法時最常用的前綴之一。
動詞“find”
告訴 Spring Data 生成一個select
查詢。另一方面,關鍵字“By”
充當where
子句,過濾返回的結果。
接下來,讓我們添加一個派生查詢方法,通過名字獲取一個人到我們的PersonRepository
:
Person findByFirstName(String firstName);
正如我們所看到的,我們的方法返回一個Person
對象。現在,讓我們為findByFirstName()
添加一個測試用例:
@Test
void givenFirstName_whenCallingFindByFirstName_ThenReturnOnePerson() {
Person person = personRepository.findByFirstName("Azhrioun");
assertNotNull(person);
assertEquals("Abderrahim", person.getLastName());
}
現在我們已經了解瞭如何使用findBy
創建返回單個對象的查詢方法,讓我們看看是否可以使用它來獲取對象列表。為此,我們將向PersonRepository
添加另一個查詢方法:
List<Person> findByLastName(String lastName);
顧名思義,這個新方法將幫助我們找到所有具有相同姓氏的對象。
同樣,讓我們使用另一個測試用例來測試findByLastName()
:
@Test
void givenLastName_whenCallingFindByLastName_ThenReturnList() {
List<Person> person = personRepository.findByLastName("Wheeler");
assertEquals(2, person.size());
}
不出所料,測試成功通過。
簡而言之,我們可以使用findBy
來獲取一個對像或一組對象。
這裡的區別在於查詢方法的返回類型。 Spring Data 通過查看返回類型來決定是返回一個還是多個對象。
3.2.按前綴findOneBy
通常, findOneBy
只是findBy
的特定變體。它明確表示要準確查找一條記錄的意圖。那麼,讓我們看看它的實際效果:
Person findOneByFirstName(String firstName);
接下來,我們將添加另一個測試來確認我們的方法工作正常:
@Test
void givenFirstName_whenCallingFindOneByFirstName_ThenReturnOnePerson() {
Person person = personRepository.findOneByFirstName("Azhrioun");
assertNotNull(person);
assertEquals("Abderrahim", person.getLastName());
}
現在,如果我們使用findOneBy
來獲取對象列表會發生什麼?讓我們來看看吧!
首先,我們將添加另一個查詢方法來查找具有相同姓氏的所有Person
對象:
List<Person> findOneByLastName(String lastName);
接下來,讓我們使用測試用例來測試我們的方法:
@Test
void givenLastName_whenCallingFindOneByLastName_ThenReturnList() {
List<Person> persons = personRepository.findOneByLastName("Wheeler");
assertEquals(2, persons.size());
}
如上所示, findOneByLastName()
返回一個列表,沒有拋出任何異常。
從技術角度來看, findOneBy
和findBy.
但是,創建一個返回帶有findOneBy
前綴的集合的查詢方法在語義上沒有意義.
簡而言之,前綴findOneBy
僅提供了需要返回一個對象的語義描述。
Spring Data 依賴此正則表達式來忽略動詞“find”
和關鍵字“By”
之間的所有字符。所以, findBy
、 findOneBy
、 findXyzBy
... 都是相似的。
使用find
關鍵字創建派生查詢方法時需要記住幾個關鍵點:
- 派生查詢方法的重要部分是關鍵字
find
和By
。 - 我們可以在
find
和By
之間添加單詞來在語義上表示某些內容。 - Spring Data 根據方法的返回類型決定返回一個對像或一個集合。
4. IncorrectResultSizeDataAccessException
這裡需要提到的一個重要警告是,當返回的結果不是預期的大小時findByLastName()
和findOneByLastName()
方法都會拋出IncorrectResultSizeDataAccessException
。
例如,如果存在多個具有給定名字的Person
對象Person findByFirstName(String firstName)
將引發異常。
因此,讓我們使用測試用例來確認這一點:
@Test
void givenFirstName_whenCallingFindByFirstName_ThenThrowException() {
IncorrectResultSizeDataAccessException exception = assertThrows(IncorrectResultSizeDataAccessException.class, () -> personRepository.findByFirstName("Stella"));
assertEquals("query did not return a unique result: 2", exception.getMessage());
}
異常的原因是,即使我們聲明我們的方法返回一個對象,執行的查詢也會返回多條記錄。
類似地,讓我們使用測試用例確認findOneByFirstName()
拋出IncorrectResultSizeDataAccessException
:
@Test
void givenFirstName_whenCallingFindOneByFirstName_ThenThrowException() {
IncorrectResultSizeDataAccessException exception = assertThrows(IncorrectResultSizeDataAccessException.class, () -> personRepository.findOneByFirstName("Stella"));
assertEquals("query did not return a unique result: 2", exception.getMessage());
}
5. 結論
在本文中,我們詳細探討了 Spring Data JPA 中findBy
和findOneBy
前綴的異同。
一路上,我們解釋了 Spring Data JPA 中的派生查詢方法。然後,我們強調,儘管findBy
和findOneBy
之間的語義意圖不同,但它們在本質上是相同的。
最後,我們展示瞭如果我們選擇錯誤的返回類型,兩者都會拋出IncorrectResultSizeDataAccessException
。
與往常一樣,本文的完整代碼可以在 GitHub 上找到。