Spring Data JPA 中 findBy 和 findOneBy 的区别

1、概览

Spring Data Repository 提供了大量可简化数据访问逻辑实现的方法。然而,选择合适的方法并不总是像我们想象的那么容易。

findByfindOneBy 为前缀的方法就是一个例子。尽管从名称上看,它们似乎做着同样的事情,但其实还是有一些区别的。

2、Spring Data 中的派生查询方法

Spring Data JPA 的派生查询方法功能经常受到称赞。这些方法提供了一种从方法名称派生特定查询的方法。例如,如果我们想通过 foo 属性检索数据,只需写 findByFoo() 即可。

通常,我们可以使用多个前缀来构建派生查询方法。这些前缀包括 findByfindOneBy。下面,我们就来看看它们的实际应用。

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() 返回一个列表,没有抛出任何异常。

从技术角度看,findOneByfindBy 没有区别。但是,创建一个带 findOneBy 前缀的查询方法来返回一个集合,在语义上是说不通的。

简而言之, findOneBy 前缀仅提供了返回一个对象的语义描述。

Spring Data 依靠这种 正则表达式 忽略动词 find 和关键字 By 之间的所有字符。因此,findByfindOneByfindXyzBy…都是类似的。

在使用 find 关键字创建派生查询方法时,有几个要点需要注意:

  • 派生查询方法的重要部分是关键字 findBy
  • 我们可以在 findBy 之间添加一些词来表示语义上的东西。
  • Spring Data 会根据方法的返回类型决定返回一个对象还是一个集合。

4、IncorrectResultSizeDataAccessException

这里要提到的一个重要注意事项是,当返回结果的数量不符合预期时,findByLastName()findOneByLastName() 方法都会抛出 IncorrectResultSizeDataAccessException 异常。

例如,Person findByFirstName(String firstName) 会在具有给定 firstNamePerson 对象不止一个的情况下产生异常。

因此,让我们用一个测试用例来确认一下:

@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 中 findByfindOneBy 前缀的异同。

同时,我们还解释了 Spring Data JPA 中的派生查询方法。然后,我们强调了尽管 findByfindOneBy 的语义意图不同,但它们在本质上是相同的。

最后,我们展示了如果选择了错误的返回类型,两者都会抛出 IncorrectResultSizeDataAccessException


参考:https://www.baeldung.com/spring-data-jpa-findby-vs-findoneby