查询方法
通常,您在存储库上触发的大多数数据访问操作都会导致对数据库运行查询。定义这样的查询只需在存储库接口上声明一个方法,如下例所示:
示例 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. 查询方法支持的关键字
关键字 | 示例 | 逻辑结果 |
---|---|---|
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…By
或 remove…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
}
使用
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 firstName, lastName from User u where u.emailAddress = :email")
Flux<User> findByEmailAddress(@Param("email") String email);
}
请注意,基于字符串的查询不支持分页,也不接受 Sort
、PageRequest
和 Limit
作为查询参数,因为这些查询需要重写。如果你想应用限制,请使用 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);
通过 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);
在首次执行之前,它会被评估一次,并使用一个 StandardEvaluationContext
,其中添加了两个变量 tableName
和 qualifiedTableName
。这种用法在表名本身是动态的情况下最为有用,因为它们也使用了 SpEL 表达式。
在查询字符串中使用 SpEL(Spring Expression Language,Spring 表达式语言)可以显著增强查询功能。然而,它们也可能接受大量不必要的参数。你应该确保在将字符串传递给查询之前对其进行清理,以避免对查询产生意外的更改。