11.3.1 定义查询方法

现在,SpitterRepository 需要完成的一项功能是根据给定的 username 查找 Spitter 对象。比如,我们将 SpitterRepository 接口修改为如下所示的样子:

public interface SpitterRepository extends JpaRepository<Spitter, Long> {
  Spitter findByUsername(String username);
}

这个新的 findByUserName() 非常简单,但是足以满足我们的需求。现在,该如何让 Spring Data JPA 提供这个方法的实现呢?

实际上,我们并不需要实现 findByUsername()。方法签名已经告诉 Spring Data JPA 足够的信息来创建这个方法的实现了。

当创建 Repository 实现的时候,Spring Data 会检查 Repository 接口的所有方法,解析方法的名称,并基于被持久化的对象来试图推测方法的目的。本质上,Spring Data 定义了一组小型的领域特定语言(domainspecific language,DSL),在这里,持久化的细节都是通过 Repository 方法的签名来描述的。

Spring Data 能够知道这个方法是要查找 Spitter 的,因为我们使用 Spitter 对 JpaRepository 进行了参数化。方法名 findByUsername 确定该方法需要根据 username 属性相匹配来查找 Spitter,而 username 是作为参数传递到方法中来的。另外,因为在方法签名中定义了该方法要返回一个 Spitter 对象,而不是一个集合,因此它只会查找一个 username 属性匹配的 Spitter。

findByUsername() 方法非常简单,但是 Spring Data 也能处理更加有意思的方法名称。Repository 方法是由一个动词、一个可选的主题(Subject)、关键词 By 以及一个断言所组成。在 findByUsername() 这个样例中,动词是 find,断言是 Username,主题并没有指定,暗含的主题是 Spitter。

作为编写 Repository 方法名称的样例,我们参照名为 readSpitterByFirstname-OrLastname() 的方法,看一下方法中的各个部分是如何映射的。图 11.1 展现了这个方法是如何拆分的。

我们可以看到,这里的动词是 read,与之前样例中的find有所差别。Spring Data 允许在方法名中使用四种动词:getread、find count。其中,动词 getread find 是同义的,这三个动词对应的 Repository 方法都会查询数据并返回对象。而动词 count 则会返回匹配对象的数量,而不是对象本身。.

Repository 方法的主题是可选的。它的主要目的是让你在命名方法的时候,有更多的灵活性。如果你更愿意将方法称为 readSpittersByFirstnameOrLastname() 而不是 readByFirstnameOrLastname() 的话,那么你尽可以这么做。

对于大部分场景来说,主题会被省略掉。readSpittersByFirstnameOrLastname() 与 readPuppiesByFirstnameOrLastname() 并没有什么差别,它们与 readThoseThingsWeWantByFirstnameOrLastname() 同样没有什么区别。要查询的对象类型是通过如何参数化 JpaRepository 接口来确定的,而不是方法名称中的主题。

在省略主题的时候,有一种例外情况。如果主题的名称以 Distinct 开头的话,那么在生成查询的时候会确保所返回结果集中不包含重复记录。

断言是方法名称中最为有意思的部分,它指定了限制结果集的属性。在 readByFirstnameOrLastname() 这个样例中,会通过 firstname 属性或 lastname 属性的值来限制结果。

在断言中,会有一个或多个限制结果的条件。每个条件必须引用一个属性,并且还可以指定一种比较操作。如果省略比较操作符的话,那么这暗指是一种相等比较操作。不过,我们也可以选择其他的比较操作,包括如下的种类:

  • IsAfter、After、IsGreaterThan、GreaterThan

  • IsGreaterThanEqual、GreaterThanEqual

  • IsBefore、Before、IsLessThan、LessThan

  • IsLessThanEqual、LessThanEqual

  • IsBetween、Between

  • IsNull、Null IsNotNull、NotNull

  • IsIn、In IsNotIn、NotIn

  • IsStartingWith、StartingWith、StartsWith

  • IsEndingWith、EndingWith、EndsWith

  • IsContaining、Containing、Contains

  • IsLike、Like

  • IsNotLike、NotLike

  • IsTrue、True

  • IsFalse、False

  • Is、Equals

  • IsNot、Not

要对比的属性值就是方法的参数。完整的方法签名如下所示:

List<Spitter> readByFirstnameOrLastname(String first, String last);

要处理 String 类型的属性时,条件中可能还会包含 IgnoringCase 或 IgnoresCase,这样在执行对比的时候就会不再考虑字符是大写还是小写。例如,要在 firstname 和 lastname 属性上忽略大小写,那么可以将方法签名改成如下的形式:

List<Spitter> readByFirstnameIgnoringCaseOrLastnameIgnoringCase(String first, String last);

需要注意,IgnoringCase 和 IgnoresCase 是同义的,你可以随意挑选一个最合适的。

作为 IgnoringCase/IgnoresCase 的替代方案,我们还可以在所有条件的后面添加 AllIgnoringCase 或 AllIgnoresCase,这样它就会忽略所有条件的大小写:

List<Spitter> readByFirstnameOrLastnameAllIgnoresCase(String first, String last);

注意,参数的名称是无关紧要的,但是它们的顺序必须要与方法名称中的操作符相匹配。

最后,我们还可以在方法名称的结尾处添加 OrderBy,实现结果集排序。例如,我们可以按照 lastname 属性升序排列结果集:

List<Spitter> readByFirstnameOrLastnameOrderByLastnameAsc(String first, String last);

如果要根据多个属性排序的话,只需将其依序添加到 OrderBy 中即可。例如,下面的样例中,首先会根据 lastname 升序排列,然后根据 firstname 属性降序排列:

List<Spitter> readByFirstnameOrLastnameOrderByLastnameAscFirstnameDesc(String first, String last);

可以看到,条件部分是通过 And 或者 Or 进行分割的。

我们不可能(至少很难)提供一个权威的列表,将使用 Spring Data 方法命名约定可以编写出来的方法种类全部列出来。但是,如下给出了几个符合方法命名约定的方法签名:

  • List<Pet> findPetsByBreedIn(List breed)

  • int countProductsByDiscontinuedTrue()

  • List<Order> findByShippingDateBetween(Date start, Date end)

我们只是初步体验了所能声明的方法种类,Spring Data JPA 会为我们实现这些方法。现在,我们只需知道通过使用属性名和关键字构建 Repository 方法签名,就能让 Spring Data JPA 生成方法实现,完成几乎所有能够想象到的查询。

不过,Spring Data 这个小型的 DSL 依旧有其局限性,有时候通过方法名称表达预期的查询很烦琐,甚至无法实现。如果遇到这种情形的话,Spring Data 能够让我们通过 @Query 注解来解决问题。

Last updated