Spring Data JPA 中 findBy 和 findOneBy 的区别
1、概览
Spring Data Repository 提供了大量可简化数据访问逻辑实现的方法。然而,选择合适的方法并不总是像我们想象的那么容易。
以 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;
// 标准的 get 和 set 方法
}
在这里,我们将使用 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:
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);
顾名思义,这个新方法将帮助我们找到所有具有相同 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
来获取对象列表,会发生什么呢?
首先,我们将添加另一个查询方法,以查找所有 lastName
相同的 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 findByFirstName(String firstName)
会在具有给定 firstName
的 Person
对象不止一个的情况下产生异常。
因此,让我们用一个测试用例来确认一下:
@Test
void givenFirstName_whenCallingFindByFirstName_ThenThrowException() {
IncorrectResultSizeDataAccessException exception = assertThrows(IncorrectResultSizeDataAccessException.class, () -> personRepository.findByFirstName("Stella"));
assertEquals("查询未返回唯一结果:2", exception.getMessage());
}
出现异常的原因是,尽管我们声明我们的方法只返回一个对象,但执行的查询却返回了不止一条记录。
同样,让我们使用一个测试用例来确认 findOneByFirstName()
是否抛出了 IncorrectResultSizeDataAccessException
:
@Test
void givenFirstName_whenCallingFindOneByFirstName_ThenThrowException() {
IncorrectResultSizeDataAccessException exception = assertThrows(IncorrectResultSizeDataAccessException.class, () -> personRepository.findOneByFirstName("Stella"));
assertEquals("查询未返回唯一结果:2", exception.getMessage());
}
5、 总结
在本文中,我们详细探讨了 Spring Data JPA 中 findBy
和 findOneBy
前缀的异同。
同时,我们还解释了 Spring Data JPA 中的派生查询方法。然后,我们强调了尽管 findBy
和 findOneBy
的语义意图不同,但它们在本质上是相同的。
最后,我们展示了如果选择了错误的返回类型,两者都会抛出 IncorrectResultSizeDataAccessException
。
参考:https://www.baeldung.com/spring-data-jpa-findby-vs-findoneby