查询方法
本节提供了一些关于 Spring Data JDBC 的实现和使用的具体信息。
大部分你在 Repository 上触发的数据访问操作都会导致数据库查询。定义这样的查询可以通过在 Repository 接口上声明一个方法来实现,如下例所示
interface PersonRepository extends PagingAndSortingRepository<Person, String> {
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)
}
1 | 该方法展示了查询所有具有给定 firstname 的人的查询。该查询是通过解析方法名来派生约束条件的,这些约束条件可以通过 And 和 Or 进行连接。因此,方法名会生成一个查询表达式,如 SELECT … FROM person WHERE firstname = :firstname 。 |
2 | 使用 Pageable 向数据库传递偏移量和排序参数。 |
3 | 返回一个 Slice<Person> 。选择 LIMIT+1 行以确定是否有更多数据可供消费。不支持 ResultSetExtractor 自定义。 |
4 | 运行一个分页查询,返回 Page<Person> 。仅选择给定页面范围内的 数据,并可能执行计数查询以确定总数。不支持 ResultSetExtractor 自定义。 |
5 | 根据给定条件查找单个实体。对于非唯一结果,会抛出 IncorrectResultSizeDataAccessException 异常。 |
6 | 与 <3> 不同,即使查询产生了更多结果文档,也总是会发出第一个实体。 |
7 | findByLastname 方法展示了查询所有具有给定 lastname 的人的查询。 |
8 | streamByLastname 方法返回一个 Stream ,这使得值一旦从数据库返回就可以立即使用。 |
9 | 你可以使用 Spring Expression Language 动态解析参数。在示例中,使用 Spring Security 解析当前用户的用户名。 |
下表显示了查询方法支持的关键字
关键字 | 示例 | 逻辑结果 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
查询派生仅限于可以在 WHERE 子句中使用且无需连接的属性。 |
查询查找策略
JDBC 模块支持在 @Query
注解中将查询手动定义为字符串,或在属性文件中将其定义为命名查询。
当前,从方法名称派生查询仅限于简单属性,即直接存在于聚合根中的属性。此外,这种方法仅支持 select 查询。
使用 @Query
下例展示了如何使用 @Query
声明一个查询方法
interface UserRepository extends CrudRepository<User, Long> {
@Query("select firstName, lastName from User u where u.emailAddress = :email")
User findByEmailAddress(@Param("email") String email);
}
默认情况下,将查询结果转换为实体时,使用的 RowMapper
与 Spring Data JDBC 自身生成的查询所使用的相同。你提供的查询必须与 RowMapper
期望的格式匹配。必须提供实体构造函数中使用的所有属性的列。通过 setter、wither 或字段访问设置的属性的列是可选的。结果中没有匹配列的属性将不会被设置。该查询用于填充聚合根、嵌入式实体以及一对一关系,包括原始类型数组(这些数组存储和加载为 SQL 数组类型)。对于 maps、lists、sets 和实体数组,会生成单独的查询。
一对一关系中的属性名称必须以关系名称加上 _
为前缀。例如,如果上面示例中的 User
有一个带 city
属性的 address
,则该 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);
它在第一次执行之前评估一次,并使用添加了 tableName
和 qualifiedTableName
这两个变量的 StandardEvaluationContext
。当表名本身是动态的(因为它们也使用 SpEL 表达式)时,这种用法最为有用。
Spring 完全支持基于 -parameters 编译器标志的 Java 8 参数名发现。在构建中使用此标志作为调试信息的替代方案,可以省略命名参数的 @Param 注解。 |
Spring Data JDBC 仅支持命名参数。 |
命名查询
如果在注解中没有提供查询(如前一节所述),Spring Data JDBC 将尝试查找一个命名查询。确定查询名称有两种方式。默认方式是取查询的领域类,即 Repository 的聚合根,取其简单名称,然后附加方法名称,用 .
分隔。或者,@Query
注解有一个 name
属性,可用于指定要查找的查询名称。
命名查询预计在类路径下的属性文件 META-INF/jdbc-named-queries.properties
中提供。
可以通过设置 @EnableJdbcRepositories.namedQueriesLocation
的值来更改该文件的位置。
命名查询的处理方式与注解提供的查询相同。
流式结果
当你将 Stream 指定为查询方法的返回类型时,Spring Data JDBC 会在元素可用时立即返回它们。处理大量数据时,这适用于减少延迟和内存需求。
流中包含一个到数据库的打开连接。为了避免内存泄漏,最终需要通过关闭流来关闭该连接。推荐的方法是使用 try-with-resource 子句
。这也意味着,一旦到数据库的连接关闭,流将无法获取更多元素,并可能抛出异常。
自定义 RowMapper
或 ResultSetExtractor
@Query
注解允许你指定要使用的自定义 RowMapper
或 ResultSetExtractor
。属性 rowMapperClass
和 resultSetExtractorClass
允许你指定要使用的类,这些类将使用默认构造函数实例化。或者,你可以将 rowMapperClassRef
或 resultSetExtractorClassRef
设置为你 Spring 应用程序上下文中的一个 bean 名称。
如果你不仅想为一个方法使用特定的 RowMapper
,而是为所有返回特定类型的自定义查询方法使用它,你可以注册一个 RowMapperMap
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=…) 或自定义 ResultSetExtractor 使用自定义 RowMapper 会禁用 Entity Callbacks 和 Lifecycle Events,因为结果映射可以在需要时触发自己的事件/回调。 |