1. 概述
Spring Data repositories 提供了一系列方法,简化了数据访问逻辑的实现。然而,选择合适的方法并不总是像我们预期的那样简单。
一个例子是带有前缀 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 repository 来管理 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
获取对象列表会怎样呢?让我们来看看!
首先,我们在 PersonRepository
中添加一个查找所有同姓氏的 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" 和 "By" 关键词。
- 我们可以在 "find" 和 "By" 之间添加单词以表示语义含义。
- Spring Data 根据方法的返回类型决定返回单个对象还是对象集合。
4. IncorrectResultSizeDataAccessException
这里需要提到的一个重要注意事项是,findByLastName()
和 findOneByLastName()
方法如果返回结果的大小不符合预期,都会抛出 IncorrectResultSizeDataAccessException
异常。
例如,Person findByFirstName(String firstName)
如果存在多个具有给定名字的 Person
对象,就会抛出异常。
现在,我们用一个测试用例来确认这一点:
@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 查看。