查询方法

通常,你在仓库(repository)上触发的大多数数据访问操作都会导致在数据库中执行查询。 定义这样的查询只需在仓库接口中声明一个方法即可,如下例所示:spring-doc.cadn.net.cn

示例 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 值(对于基本类型则提供默认值)。spring-doc.cadn.net.cn

下表列出了查询方法所支持的关键字:spring-doc.cadn.net.cn

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

Afterspring-doc.cadn.net.cn

findByBirthdateAfter(Date date)spring-doc.cadn.net.cn

birthdate > datespring-doc.cadn.net.cn

GreaterThanspring-doc.cadn.net.cn

findByAgeGreaterThan(int age)spring-doc.cadn.net.cn

age > agespring-doc.cadn.net.cn

GreaterThanEqualspring-doc.cadn.net.cn

findByAgeGreaterThanEqual(int age)spring-doc.cadn.net.cn

age >= agespring-doc.cadn.net.cn

Beforespring-doc.cadn.net.cn

findByBirthdateBefore(Date date)spring-doc.cadn.net.cn

birthdate < datespring-doc.cadn.net.cn

LessThanspring-doc.cadn.net.cn

findByAgeLessThan(int age)spring-doc.cadn.net.cn

age < agespring-doc.cadn.net.cn

LessThanEqualspring-doc.cadn.net.cn

findByAgeLessThanEqual(int age)spring-doc.cadn.net.cn

age <= agespring-doc.cadn.net.cn

Betweenspring-doc.cadn.net.cn

findByAgeBetween(int from, int to)spring-doc.cadn.net.cn

age BETWEEN from AND tospring-doc.cadn.net.cn

NotBetweenspring-doc.cadn.net.cn

findByAgeNotBetween(int from, int to)spring-doc.cadn.net.cn

age NOT BETWEEN from AND tospring-doc.cadn.net.cn

Inspring-doc.cadn.net.cn

findByAgeIn(Collection<Integer> ages)spring-doc.cadn.net.cn

age IN (age1, age2, ageN)spring-doc.cadn.net.cn

NotInspring-doc.cadn.net.cn

findByAgeNotIn(Collection ages)spring-doc.cadn.net.cn

age NOT IN (age1, age2, ageN)spring-doc.cadn.net.cn

IsNotNull, NotNullspring-doc.cadn.net.cn

findByFirstnameNotNull()spring-doc.cadn.net.cn

firstname IS NOT NULLspring-doc.cadn.net.cn

IsNull, Nullspring-doc.cadn.net.cn

findByFirstnameNull()spring-doc.cadn.net.cn

firstname IS NULLspring-doc.cadn.net.cn

Like, StartingWith, EndingWithspring-doc.cadn.net.cn

findByFirstnameLike(String name)spring-doc.cadn.net.cn

firstname LIKE namespring-doc.cadn.net.cn

NotLike, IsNotLikespring-doc.cadn.net.cn

findByFirstnameNotLike(String name)spring-doc.cadn.net.cn

firstname NOT LIKE namespring-doc.cadn.net.cn

Containing 用于 Stringspring-doc.cadn.net.cn

findByFirstnameContaining(String name)spring-doc.cadn.net.cn

firstname LIKE '%' + name +'%'spring-doc.cadn.net.cn

NotContaining 用于 Stringspring-doc.cadn.net.cn

findByFirstnameNotContaining(String name)spring-doc.cadn.net.cn

firstname NOT LIKE '%' + name +'%'spring-doc.cadn.net.cn

(No keyword)spring-doc.cadn.net.cn

findByFirstname(String name)spring-doc.cadn.net.cn

firstname = namespring-doc.cadn.net.cn

Notspring-doc.cadn.net.cn

findByFirstnameNot(String name)spring-doc.cadn.net.cn

firstname != namespring-doc.cadn.net.cn

IsTrue, Truespring-doc.cadn.net.cn

findByActiveIsTrue()spring-doc.cadn.net.cn

active IS TRUEspring-doc.cadn.net.cn

IsFalse, Falsespring-doc.cadn.net.cn

findByActiveIsFalse()spring-doc.cadn.net.cn

active IS FALSEspring-doc.cadn.net.cn

修改查询

前面的章节描述了如何声明查询以访问给定的实体或实体集合。 可以结合使用上表中的关键字与 delete…Byremove…By,创建用于删除匹配行的派生查询。spring-doc.cadn.net.cn

示例 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 注解来进行修改,如下例所示:spring-doc.cadn.net.cn

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

修改查询的结果可以是:spring-doc.cadn.net.cn

@Modifying 注解仅在与 @Query 注解结合使用时才有意义。 派生的自定义方法不需要此注解。spring-doc.cadn.net.cn

修改型查询直接在数据库上执行。 不会触发任何事件或回调。 因此,如果带审计注解的字段未在注解查询中显式更新,这些字段也不会被更新。spring-doc.cadn.net.cn

或者,您可以使用Spring Data 仓库的自定义实现中描述的功能来添加自定义的修改行为。spring-doc.cadn.net.cn

使用@Query

以下示例展示了如何使用 @Query 来声明一个查询方法:spring-doc.cadn.net.cn

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

使用 SpEL 表达式的查询

查询字符串定义可以与 SpEL 表达式结合使用,以在运行时创建动态查询。 SpEL 表达式有两种使用方式。spring-doc.cadn.net.cn

SpEL 表达式可以提供谓词值,这些值会在运行查询之前进行求值。spring-doc.cadn.net.cn

表达式通过一个包含所有参数的数组来暴露方法参数。 以下查询使用 [0] 来声明 lastname 的谓词值(这等同于 :lastname 参数绑定):spring-doc.cadn.net.cn

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

此表达式支持可通过查询 SPI 进行扩展:org.springframework.data.spel.spi.EvaluationContextExtension。 该查询 SPI 可以添加属性和函数,并可自定义根对象。 在构建查询时进行 SpEL 表达式求值的过程中,扩展会从应用上下文中获取。spring-doc.cadn.net.cn

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

在查询语句中间使用表达式的另一种方式,与参数无关。 对表达式求值的结果将替换查询字符串中的该表达式。spring-doc.cadn.net.cn

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

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

查询字符串中的 SpEL 是一种增强查询功能的强大方式。 然而,它们也可能接受大量不希望传入的参数。 在将字符串传递给查询之前,应确保对其进行清理,以避免对查询造成意外的修改。spring-doc.cadn.net.cn