查询方法

您通常在仓库上触发的多数数据访问操作都会导致对数据库运行查询。定义此类查询的方法是在仓库接口上声明一个方法,如下例所示。

示例 1. 带有查询方法的 PersonRepository
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {

  Flux<Person> findByFirstname(String firstname);                                   (1)

  Flux<Person> findByFirstname(Publisher<String> firstname);                        (2)

  Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); (3)

  Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);       (4)

  Mono<Person> findFirstByLastname(String lastname);                                (5)

  @Query("SELECT * FROM person WHERE lastname = :lastname")
  Flux<Person> findByLastname(String lastname);                                     (6)

  @Query("SELECT firstname, lastname FROM person WHERE lastname = $1")
  Mono<Person> findFirstByLastname(String lastname);                                (7)
}
1 此方法显示了针对具有给定 `firstname` 的所有人的查询。通过解析方法名称来派生查询,以便将约束与 `And` 和 `Or` 连接起来。因此,方法名称会生成 `SELECT … FROM person WHERE firstname = :firstname` 的查询表达式。
2 给定 `Publisher` 发出 `firstname` 后,此方法将显示针对具有给定 `firstname` 的所有人的查询。
3 使用 `Pageable` 将偏移量和排序参数传递到数据库。
4 查找满足给定条件的单个实体。如果结果不唯一,则以 `IncorrectResultSizeDataAccessException` 结束。
5 除非 <4>,否则即使查询产生更多结果行,也会始终发出第一个实体。
6 `findByLastname` 方法显示了针对具有给定姓氏的所有人的查询。
7 查询单个 `Person` 实体,只投影 `firstname` 和 `lastname` 列。带注释的查询使用本机绑定标记,在本例中是 Postgres 绑定标记。

请注意,在 `@Query` 注解中使用的 select 语句的列必须与 `NamingStrategy` 为相应属性生成的名称匹配。如果 select 语句不包含匹配的列,则不会设置该属性。如果持久性构造函数需要该属性,则提供 null 或(对于基本类型)默认值。

下表显示了查询方法支持的关键字。

表 1. 查询方法支持的关键字
关键字 示例 逻辑结果

After

findByBirthdateAfter(Date date)

birthdate > date

GreaterThan

findByAgeGreaterThan(int age)

age > age

GreaterThanEqual

findByAgeGreaterThanEqual(int age)

age >= age

Before

findByBirthdateBefore(Date date)

birthdate < date

LessThan

findByAgeLessThan(int age)

age < age

LessThanEqual

findByAgeLessThanEqual(int age)

age <= age

Between

findByAgeBetween(int from, int to)

age BETWEEN from AND to

NotBetween

findByAgeNotBetween(int from, int to)

age NOT BETWEEN from AND to

In

findByAgeIn(Collection<Integer> ages)

age IN (age1, age2, ageN)

NotIn

findByAgeNotIn(Collection ages)

age NOT IN (age1, age2, ageN)

`IsNotNull`,`NotNull`

findByFirstnameNotNull()

firstname IS NOT NULL

`IsNull`,`Null`

findByFirstnameNull()

firstname IS NULL

`Like`,`StartingWith`,`EndingWith`

findByFirstnameLike(String name)

firstname LIKE name

`NotLike`,`IsNotLike`

findByFirstnameNotLike(String name)

firstname NOT LIKE name

字符串上的 `Containing`

findByFirstnameContaining(String name)

firstname LIKE '%' + name +'%'

字符串上的 `NotContaining`

findByFirstnameNotContaining(String name)

firstname NOT LIKE '%' + name +'%'

(无关键字)

findByFirstname(String name)

firstname = name

Not

findByFirstnameNot(String name)

firstname != name

`IsTrue`,`True`

findByActiveIsTrue()

active IS TRUE

`IsFalse`,`False`

findByActiveIsFalse()

active IS FALSE

修改查询

前面几节介绍了如何声明查询以访问给定的实体或实体集合。可以将前面表格中的关键字与 `delete…By` 或 `remove…By` 结合使用,以创建删除匹配行的派生查询。

示例 2. `Delete…By` 查询
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {

  Mono<Integer> deleteByLastname(String lastname);            (1)

  Mono<Void> deletePersonByLastname(String lastname);         (2)

  Mono<Boolean> deletePersonByLastname(String lastname);      (3)
}
1 使用 `Mono<Integer>` 的返回类型将返回受影响的行数。
2 使用 `Void` 只报告行是否已成功删除,而不发出结果值。
3 使用 `Boolean` 报告是否删除了至少一行。

由于这种方法对于全面的自定义功能是可行的,因此您可以通过使用 `@Modifying` 注解查询方法来修改只需要参数绑定的查询,如下例所示。

@Modifying
@Query("UPDATE person SET firstname = :firstname where lastname = :lastname")
Mono<Integer> setFixedFirstnameFor(String firstname, String lastname);

修改查询的结果可以是:

  • `Void`(或 Kotlin `Unit`)以丢弃更新计数并等待完成。

  • `Integer` 或其他数字类型,发出受影响的行数。

  • `Boolean`,发出是否更新了至少一行的信息。

`@Modifying` 注解仅与 `@Query` 注解结合使用时才相关。派生的自定义方法不需要此注解。

修改查询直接针对数据库执行。不会调用任何事件或回调。因此,如果在带注释的查询中未更新具有审计注解的字段,则也不会更新这些字段。

或者,您可以使用Spring Data 仓库的自定义实现中描述的工具添加自定义修改行为。

使用 `@Query`

以下示例显示了如何使用 `@Query` 声明查询方法。

使用 `@Query` 声明查询方法。
interface UserRepository extends ReactiveCrudRepository<User, Long> {

  @Query("select firstName, lastName from User u where u.emailAddress = :email")
  Flux<User> findByEmailAddress(@Param("email") String email);
}
请注意,基于字符串的查询不支持分页,也不接受 `Sort`、`PageRequest` 和 `Limit` 作为查询参数,因为对于这些查询,需要重写查询。如果您想应用限制,请使用 SQL 表达此意图,并自行将适当的参数绑定到查询。
Spring 完全支持基于 `-parameters` 编译器标志的 Java 8 的参数名称发现。通过在构建中使用此标志作为调试信息的替代方法,您可以省略命名参数的 `@Param` 注解。

使用 SpEL 表达式的查询

查询字符串定义可以与 SpEL 表达式一起使用,以便在运行时创建动态查询。SpEL 表达式可以使用两种方式。

SpEL 表达式可以提供谓词值,这些值在运行查询之前进行评估。

表达式通过包含所有参数的数组公开方法参数。以下查询使用 `[0]` 声明 `lastname` 的谓词值(相当于 `:lastname` 参数绑定)。

@Query("SELECT * FROM person WHERE lastname = :#{[0]}")
Flux<Person> findByQueryWithParameterExpression(String lastname);

表达式支持可以通过查询 SPI 扩展:org.springframework.data.spel.spi.EvaluationContextExtension。查询 SPI 可以贡献属性和函数,并可以自定义根对象。在构建查询时进行 SpEL 评估时,扩展会从应用程序上下文检索。

当将 SpEL 表达式与普通参数结合使用时,请使用命名参数表示法而不是本地绑定标记,以确保正确的绑定顺序。

另一种使用表达式的方法是在查询中间,独立于参数。查询的评估结果将替换查询字符串中的表达式。

在查询中使用 SpEL
@Query("SELECT * FROM #{tableName} WHERE lastname = :lastname")
Flux<Person> findByQueryWithExpression(String lastname);

它在第一次执行之前评估一次,并使用一个StandardEvaluationContext,其中添加了两个变量tableNamequalifiedTableName。当表名本身也是动态的时,这种用法最为有用,因为它们也使用 SpEL 表达式。

查询字符串中的 SpEL 可以有效增强查询。但是,它们也可能接受各种不需要的参数。在将字符串传递给查询以避免对查询进行不需要的更改之前,应确保对其进行清理。