跳到主要内容

查询方法

DeepSeek V3 中英对照 Query Methods

通常,您在存储库上触发的大多数数据访问操作都会导致对数据库运行查询。定义这样的查询只需在存储库接口上声明一个方法,如下例所示:

示例 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
}
java
  • 该方法展示了如何查询所有具有给定 firstname 的人。查询是通过解析方法名称来构建的,解析出的条件可以通过 AndOr 进行连接。因此,方法名称会生成一个查询表达式:SELECT … FROM person WHERE firstname = :firstname

  • 该方法展示了如何在给定的 Publisher 发出 firstname 时,查询所有具有该 firstname 的人。

  • 使用 Pageable 向数据库传递偏移量和排序参数。

  • 根据给定的条件查找单个实体。如果结果不唯一,则会抛出 IncorrectResultSizeDataAccessException 异常。

  • 除非 <4>,否则即使查询返回了多行结果,也总是会发出第一个实体。

  • findByLastname 方法展示了如何查询所有具有给定姓氏的人。

  • 查询单个 Person 实体的方法,仅投影 firstnamelastname 列。注解的查询使用了原生绑定标记,在本例中是 Postgres 的绑定标记。

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

下表展示了查询方法支持的关键字:

表 1. 查询方法支持的关键字

关键字示例逻辑结果
AfterfindByBirthdateAfter(Date date)birthdate > date
GreaterThanfindByAgeGreaterThan(int age)age > age
GreaterThanEqualfindByAgeGreaterThanEqual(int age)age >= age
BeforefindByBirthdateBefore(Date date)birthdate < date
LessThanfindByAgeLessThan(int age)age < age
LessThanEqualfindByAgeLessThanEqual(int age)age <= age
BetweenfindByAgeBetween(int from, int to)age BETWEEN from AND to
NotBetweenfindByAgeNotBetween(int from, int to)age NOT BETWEEN from AND to
InfindByAgeIn(Collection<Integer> ages)age IN (age1, age2, ageN)
NotInfindByAgeNotIn(Collection ages)age NOT IN (age1, age2, ageN)
IsNotNull, NotNullfindByFirstnameNotNull()firstname IS NOT NULL
IsNull, NullfindByFirstnameNull()firstname IS NULL
Like, StartingWith, EndingWithfindByFirstnameLike(String name)firstname LIKE name
NotLike, IsNotLikefindByFirstnameNotLike(String name)firstname NOT LIKE name
Containing on StringfindByFirstnameContaining(String name)firstname LIKE '%' + name +'%'
NotContaining on StringfindByFirstnameNotContaining(String name)firstname NOT LIKE '%' + name +'%'
(无关键字)findByFirstname(String name)firstname = name
NotfindByFirstnameNot(String name)firstname != name
IsTrue, TruefindByActiveIsTrue()active IS TRUE
IsFalse, FalsefindByActiveIsFalse()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
}
java
  • 使用 Mono<Integer> 作为返回类型会返回受影响的行数。

  • 使用 Void 只会报告行是否成功删除,而不会发出结果值。

  • 使用 Boolean 会报告是否至少有一行被删除。

由于这种方法适用于全面的自定义功能,你可以通过使用 @Modifying 注解来修改只需要参数绑定的查询方法,如下例所示:

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

修改查询的结果可以是:

  • Void(或 Kotlin 的 Unit)用于丢弃更新计数并等待完成。

  • Integer 或其他数字类型,用于返回受影响的行数。

  • Boolean 用于返回是否至少有一行被更新。

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

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

或者,您也可以通过使用 Spring Data 存储库的自定义实现 中描述的功能来添加自定义修改行为。

使用 @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);
}
java
注意

请注意,基于字符串的查询不支持分页,也不接受 SortPageRequestLimit 作为查询参数,因为这些查询需要重写。如果你想应用限制,请使用 SQL 表达此意图,并自行将适当的参数绑定到查询中。

备注

Spring 完全支持基于 Java 8 的 -parameters 编译器标志的参数名称发现。通过在构建中使用此标志作为调试信息的替代方案,您可以省略命名参数的 @Param 注解。

使用 SpEL 表达式的查询

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

SpEL 表达式可以提供在运行查询之前被评估的谓词值。

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

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

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

:::提示
当将 SpEL 表达式与普通参数结合使用时,请使用命名参数符号而非原生绑定标记,以确保正确的绑定顺序。
:::

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

@Query("SELECT * FROM #{tableName} WHERE lastname = :lastname")
Flux<Person> findByQueryWithExpression(String lastname);
java

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

在查询字符串中使用 SpEL(Spring Expression Language,Spring 表达式语言)可以显著增强查询功能。然而,它们也可能接受大量不必要的参数。你应该确保在将字符串传递给查询之前对其进行清理,以避免对查询产生意外的更改。