跳到主要内容

查询方法

Doubao 中英对照 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 的所有人。该查询是通过解析方法名中可由 And 和 Or 连接的约束而派生出来的。因此,方法名产生如下查询表达式:SELECT … FROM person WHERE firstname = :firstname。

  • 该方法展示了一条查询:一旦给定 Publisher 发出 firstname,就查找具有该 firstname 的所有人。

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

  • 按给定条件查找单个实体。当结果不唯一时,它会以 IncorrectResultSizeDataAccessException 完成。

  • 与 <4> 不同,即使查询产生更多结果行,也始终发出第一个实体。

  • findByLastname 方法展示了一条查询,用于查找具有给定姓氏的所有人。

  • 一条查询单个 Person 实体、仅投影 firstname 和 lastname 列的查询。该注解查询使用原生绑定标记,本例中即 Postgres 绑定标记。

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

下表列出查询方法所支持的关键词:

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

关键词示例逻辑结果
AndfindByLastnameAndFirstname… WHERE lastname = :lastname AND firstname = :firstname
OrfindByLastnameOrFirstname… WHERE lastname = :lastname OR firstname = :firstname
Is, EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… WHERE firstname = :firstname(或参数为 null 时为 … WHERE firstname IS NULL)
BetweenfindByStartDateBetween… WHERE startDate BETWEEN :startDate and :startDate1
LessThanfindByAgeLessThan… WHERE age < :age
LessThanEqualfindByAgeLessThanEqual… WHERE age <= :age
GreaterThanfindByAgeGreaterThan… WHERE age > :age
GreaterThanEqualfindByAgeGreaterThanEqual… WHERE age >= :age
AfterfindByStartDateAfter… WHERE startDate > :startDate
BeforefindByStartDateBefore… WHERE startDate < :startDate
IsNull, NullfindByAge(Is)Null… WHERE age IS NULL
IsNotNull, NotNullfindByAge(Is)NotNull… WHERE age IS NOT NULL
LikefindByFirstnameLike… WHERE firstname LIKE :firstname
NotLikefindByFirstnameNotLike… WHERE firstname NOT LIKE :firstname
StartingWithfindByFirstnameStartingWith… WHERE firstname LIKE :firstname(参数绑定后追加 %)
EndingWithfindByFirstnameEndingWith… WHERE firstname LIKE :firstname(参数绑定前追加 %)
ContainingfindByFirstnameContaining… WHERE firstname LIKE :firstname(参数用 % 包裹绑定)
OrderByfindByAgeOrderByLastnameDesc… WHERE age = :age ORDER BY lastname DESC
NotfindByLastnameNot… WHERE lastname <> :lastname(或参数为 null 时为 … WHERE lastname IS NOT NULL)
InfindByAgeIn(Collection<Age> ages)… WHERE age IN (:ages)
NotInfindByAgeNotIn(Collection<Age> ages)… WHERE age NOT IN (:ages)
TruefindByActiveTrue()… WHERE active = :active(其中 active 为 TRUE)
FalsefindByActiveFalse()… WHERE active = :active(其中 active 为 FALSE)
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(firstname) = UPPER(:firstname)

修改查询​

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

示例 2. Delete…By 查询

interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {

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 first_name, last_name FROM user WHERE email_address = :email")
Flux<User> findByEmailAddress(@Param("email") String email);
}
java
注意

请注意,基于字符串的查询不支持分页,也不接受 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);
java

这种表达式支持可通过查询 SPI 扩展:org.springframework.data.spel.spi.EvaluationContextExtension。查询 SPI 可以贡献属性和函数,并可以定制根对象。扩展在构建查询、SpEL 求值时从应用上下文中获取。

提示

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

使用表达式的另一种方式是在查询语句中间、独立于参数使用。对查询求值的结果将替换查询字符串中的该表达式。

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

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

查询字符串中的 SpEL 可以成为增强查询的有力手段。不过,它们也可能接受范围广泛的不想要的参数。您应确保在将字符串传递给查询之前对其进行清理,以避免对查询产生不想要的更改。