查询方法
本节提供有关 Spring Data JDBC 实现和使用的一些特定信息。
您通常在仓库上触发的大多数数据访问操作都会导致对数据库运行一条查询。定义这样的查询,只需在仓库接口上声明一个方法,如下例所示:
interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
List<Person> findByFirstname(String firstname); 1
List<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); 2
Slice<Person> findByLastname(String lastname, Pageable pageable); 3
Page<Person> findByLastname(String lastname, Pageable pageable); 4
Person findByFirstnameAndLastname(String firstname, String lastname); 5
Person findFirstByLastname(String lastname); 6
@Query("SELECT * FROM person WHERE lastname = :lastname")
List<Person> findByLastname(String lastname); 7
@Query("SELECT * FROM person WHERE lastname = :lastname")
Stream<Person> streamByLastname(String lastname); 8
@Query("SELECT * FROM person WHERE username = :#{ principal?.username }")
Person findActiveUser(); 9
}
该方法展示了查询所有具有给定
firstname的人的查询。该查询通过解析方法名中可用And和Or连接的约束来派生。因此,方法名产生如下查询表达式:SELECT … FROM person WHERE firstname = :firstname。使用
Pageable向数据库传递偏移量和排序参数。返回
Slice<Person>。选取LIMIT+1行以判断是否还有更多数据可消费。不支持ResultSetExtractor自定义。运行一个返回
Page<Person>的分页查询。仅选取给定页范围内的数据,并可能额外运行一个计数查询以确定总数。不支持ResultSetExtractor自定义。查找符合给定条件的单个实体。当结果不唯一时,以
IncorrectResultSizeDataAccessException结束。与 <5> 不同,即使查询产生更多结果,也始终发出第一个实体。
findByLastname方法展示了查询所有具有给定lastname的人的查询。streamByLastname方法返回一个Stream,使得值一从数据库返回即可使用。您可以使用 Spring 表达式语言动态解析参数。在示例中,使用 Spring Security 来解析当前用户的用户名。
下表显示了查询方法支持的关键词:
表 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) |
查询派生仅限于可在 WHERE 子句中使用、且无需连接的属性。
查询查找策略
JDBC 模块支持在 @Query 注解中以字符串形式手动定义查询,或在属性文件中以命名查询的形式定义。
目前,从方法名派生查询仅限于简单属性,即直接存在于聚合根中的属性。此外,这种方式仅支持 select 查询。
使用 @Query
以下示例展示了如何使用 @Query 来声明查询方法:
interface UserRepository extends CrudRepository<User, Long> {
@Query("SELECT first_name, last_name FROM user WHERE email_address = :email")
User findByEmailAddress(@Param("email") String email);
}
为将查询结果转换为实体,默认使用与 Spring Data JDBC 自身生成的查询相同的 RowMapper。您提供的查询必须符合 RowMapper 所期望的格式。必须提供实体构造函数中所用所有属性对应的列。通过 setter、wither 或字段访问设置的属性对应的列是可选的。结果中没有匹配列的属性将不会被设置。该查询用于填充聚合根、内嵌实体和一对一关系(包括以 SQL 数组类型存储和加载的基本类型数组)。对于实体的映射、列表、集合和数组,会生成单独的查询。
一对一关系属性的名称必须以关系名加 _ 作为前缀。例如,如果上面示例中的 User 有一个 address,其属性为 city,那么该 city 对应的列必须命名为 address_city。
请注意,基于字符串的查询不支持分页,也不接受 Sort、PageRequest 和 Limit 作为查询参数,因为对于这些查询,查询需要被重写。如果您想应用限制,请用 SQL 表达此意图,并自行将相应参数绑定到查询。
查询可以包含 SpEL 表达式。有两种变体,其求值方式不同。
在第一种变体中,SpEL 表达式以 : 为前缀,像绑定变量一样使用。这样的 SpEL 表达式会被替换为一个绑定变量,该变量绑定到 SpEL 表达式的结果。
@Query("SELECT * FROM person WHERE id = :#{#person.id}")
Person findWithSpEL(PersonRef person);
如上面示例所示,这可用于访问参数的成员。对于更复杂的用例,可以在应用程序上下文中提供一个 EvaluationContextExtension,从而在 SpEL 中使任意对象可用。
另一种变体可用于查询中的任意位置,查询的求值结果将替换查询字符串中的该表达式。
@Query("SELECT * FROM #{tableName} WHERE id = :id")
Person findWithSpEL(PersonRef person);
它在第一次执行之前求值一次,并使用一个 StandardEvaluationContext,其中添加了 tableName 和 qualifiedTableName 两个变量。当表名本身是动态的(因为它们也使用 SpEL 表达式)时,这种用法最为有用。
Spring 完全支持 Java 8 基于 -parameters 编译器标志的参数名发现。通过在构建中使用此标志作为调试信息的替代,您可以为命名参数省略 @Param 注解。
Spring Data JDBC 仅支持命名参数。
命名查询
如果如上一节所述,注解中未给出查询,Spring Data JDBC 将尝试查找命名查询。查询名称的确定方式有两种。默认方式是取查询的_领域类_(即仓库的聚合根),取其简单名,并追加方法名,二者以 . 分隔。或者,@Query 注解有一个 name 属性,可用于指定要查找的查询的名称。
命名查询应提供在 classpath 上的属性文件 META-INF/jdbc-named-queries.properties 中。
可以通过给 @EnableJdbcRepositories.namedQueriesLocation 设置值来更改该文件的位置。
命名查询的处理方式与注解提供的查询相同。
自定义查询方法
流式结果
当您将 Stream 指定为查询方法的返回类型时,Spring Data JDBC 会在元素可用时立即返回。在处理大量数据时,这适合降低延迟和内存需求。
该流持有一个到数据库的打开连接。为避免内存泄漏,最终需要通过关闭流来关闭该连接。推荐的方式是 try-with-resource 语句。这也意味着,一旦到数据库的连接关闭,该流就无法再获取更多元素,并可能抛出异常。
自定义 RowMapper 或 ResultSetExtractor
@Query 注解允许您指定要使用的自定义 RowMapper 或 ResultSetExtractor。rowMapperClass 和 resultSetExtractorClass 属性允许您指定要使用的类,它们会通过默认构造函数实例化。或者,您可以将 rowMapperRef 或 resultSetExtractorRef 设置为来自 Spring 应用程序上下文的 bean 名称。
如果您希望某个 RowMapper 不仅用于单个方法,而是用于所有返回特定类型的自定义查询方法,可以注册一个 QueryMappingConfiguration bean,并为每个方法返回类型注册一个 RowMapper。以下示例展示了如何注册 DefaultQueryMappingConfiguration:
@Bean
QueryMappingConfiguration rowMappers() {
return new DefaultQueryMappingConfiguration()
.register(Person.class, new PersonRowMapper())
.register(Address.class, new AddressRowMapper());
}
在确定方法使用哪个 RowMapper 时,会根据方法的返回类型遵循以下步骤:
-
如果该类型是简单类型,则不使用
RowMapper。相反,查询预期返回一行一列,并对该值应用到返回类型的转换。
-
迭代
QueryMappingConfiguration中的实体类,直到找到一个是所讨论返回类型的超类或接口的类。使用为该类注册的RowMapper。按注册顺序迭代,因此请确保在具体类型之后注册更通用的类型。
如果适用,集合或 Optional 等包装类型会被解包。因此,Optional<Person> 的返回类型在上述过程中使用 Person 类型。
通过 QueryMappingConfiguration、@Query(rowMapperClass=…) 使用自定义 RowMapper,或使用自定义 ResultSetExtractor,会禁用实体回调和生命周期事件,因为结果映射可以在需要时自行发出事件/回调。
修改查询
您可以通过在查询方法上使用 @Modifying 来将查询标记为修改查询,如下例所示:
@Modifying
@Query("UPDATE DUMMYENTITY SET name = :name WHERE id = :id")
boolean updateName(@Param("id") Long id, @Param("name") String name);
您可以指定以下返回类型:
-
void -
int(更新的记录数) -
boolean(是否更新了一条记录)
修改查询直接对数据库执行。不会调用任何事件或回调。因此,如果带审计注解的字段在注解查询中没有被更新,它们也不会被更新。