查询方法

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

示例 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 查询所有人员。该查询是通过解析方法名,查找可以用 AndOr 连接的约束来派生的。因此,方法名会生成一个查询表达式 SELECT … FROM person WHERE firstname = :firstname
2 该方法展示了根据给定的 firstname 查询所有人员,一旦 firstname 由给定的 Publisher 发出。
3 使用 Pageable 向数据库传递偏移量和排序参数。
4 根据给定的条件查找单个实体。如果结果不唯一,则抛出 IncorrectResultSizeDataAccessException 异常。
5 除非 <4>,即使查询产生更多结果行,也始终发出第一个实体。
6 findByLastname 方法展示了根据给定的姓氏查询所有人员。
7 查询单个 Person 实体,仅投影 firstnamelastname 列。带注解的查询使用原生绑定标记,在此示例中为 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 on String

findByFirstnameContaining(String name)

firstname LIKE '%' + name +'%'

NotContaining on String

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…Byremove…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 Repositories 的自定义实现中描述的功能来添加自定义修改行为。

使用 @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);
}
请注意,基于 String 的查询不支持分页,也不接受 SortPageRequestLimit 作为查询参数,因为对于这些查询,需要重写查询。如果您想应用限制,请使用 SQL 表达此意图并自行将适当的参数绑定到查询。
Spring 完全支持基于 -parameters 编译器标志的 Java 8 参数名称发现。在您的构建中使用此标志作为调试信息的替代方案,可以省略命名参数的 @Param 注解。

包含 SpEL 表达式的查询

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

SpEL 表达式可以提供断言值 (predicate values),这些值在运行查询之前进行评估。

表达式通过一个包含所有参数的数组暴露方法参数。以下查询使用 [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);

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

在查询字符串中使用 SpEL 是增强查询的强大方法。但是,它们也可能接受各种不必要的参数。在将字符串传递给查询之前,您应该确保对其进行净化(sanitize),以避免对查询造成不必要的更改。