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 上找到。