查询方法
您通常在仓库上触发的大多数数据访问操作,都会导致对数据库运行一条查询。定义这样的查询只需在仓库接口上声明一个方法,如下例所示:
示例 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
}
该方法展示了一条查询,用于查找具有给定
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. 查询方法支持的关键词
| 关键词 | 示例 | 逻辑结果 |
|---|---|---|
And | findByLastnameAndFirstname | … WHERE lastname = :lastname AND firstname = :firstname |
Or | findByLastnameOrFirstname | … WHERE lastname = :lastname OR firstname = :firstname |
Is, Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … WHERE firstname = :firstname(或参数为 null 时为 … WHERE firstname IS NULL) |
Between | findByStartDateBetween | … WHERE startDate BETWEEN :startDate and :startDate1 |
LessThan | findByAgeLessThan | … WHERE age < :age |
LessThanEqual | findByAgeLessThanEqual | … WHERE age <= :age |
GreaterThan | findByAgeGreaterThan | … WHERE age > :age |
GreaterThanEqual | findByAgeGreaterThanEqual | … WHERE age >= :age |
After | findByStartDateAfter | … WHERE startDate > :startDate |
Before | findByStartDateBefore | … WHERE startDate < :startDate |
IsNull, Null | findByAge(Is)Null | … WHERE age IS NULL |
IsNotNull, NotNull | findByAge(Is)NotNull | … WHERE age IS NOT NULL |
Like | findByFirstnameLike | … WHERE firstname LIKE :firstname |
NotLike | findByFirstnameNotLike | … WHERE firstname NOT LIKE :firstname |
StartingWith | findByFirstnameStartingWith | … WHERE firstname LIKE :firstname(参数绑定后追加 %) |
EndingWith | findByFirstnameEndingWith | … WHERE firstname LIKE :firstname(参数绑定前追加 %) |
Containing | findByFirstnameContaining | … WHERE firstname LIKE :firstname(参数用 % 包裹绑定) |
OrderBy | findByAgeOrderByLastnameDesc | … WHERE age = :age ORDER BY lastname DESC |
Not | findByLastnameNot | … WHERE lastname <> :lastname(或参数为 null 时为 … WHERE lastname IS NOT NULL) |
In | findByAgeIn(Collection<Age> ages) | … WHERE age IN (:ages) |
NotIn | findByAgeNotIn(Collection<Age> ages) | … WHERE age NOT IN (:ages) |
True | findByActiveTrue() | … WHERE active = :active(其中 active 为 TRUE) |
False | findByActiveFalse() | … WHERE active = :active(其中 active 为 FALSE) |
IgnoreCase | findByFirstnameIgnoreCase | … 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
}
使用
Mono<Integer>返回类型会返回受影响的行数。使用
Void仅报告行是否成功删除,而不发出结果值。使用
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 声明一个查询方法:
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);
}
请注意,基于字符串的查询不支持分页,也不接受 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 表达式与普通参数结合使用时,请使用命名参数记法而非原生绑定标记,以确保正确的绑定顺序。
使用表达式的另一种方式是在查询语句中间、独立于参数使用。对查询求值的结果将替换查询字符串中的该表达式。
@Query("SELECT * FROM #{#tableName} WHERE lastname = :lastname")
Flux<Person> findByQueryWithExpression(String lastname);
它在首次执行之前求值一次,并使用一个 StandardEvaluationContext,其中添加了 tableName 和 qualifiedTableName 两个变量。这种用法在表名本身是动态的时最为有用,因为它们也使用 SpEL 表达式。
查询字符串中的 SpEL 可以成为增强查询的有力手段。不过,它们也可能接受范围广泛的不想要的参数。您应确保在将字符串传递给查询之前对其进行清理,以避免对查询产生不想要的更改。