查询方法
本节提供了一些关于 Spring Data JDBC 实现和使用的具体信息。
您通常在存储库上触发的大多数数据访问操作都会导致针对数据库运行查询。定义此类查询只需在存储库接口上声明一个方法,如以下示例所示
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 注解中将查询手动定义为字符串,或在属性文件中定义为命名查询。
从方法名称派生查询目前仅限于简单属性,即直接存在于聚合根中的属性。此外,此方法仅支持选择查询。
使用 @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 数组类型的原始类型数组。将为映射、列表、集合和实体数组生成单独的查询。
一对一关系的属性名称必须以关系名称加 _ 为前缀。例如,如果上述示例中的 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 将尝试定位命名查询。有两种方式可以确定查询的名称。默认方式是获取查询的领域类,即存储库的聚合根,获取其简单名称,并追加用 . 分隔的方法名称。另外,@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 允许您指定要使用的类,这些类将使用默认构造函数实例化。或者,您可以将 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=…) 使用自定义 RowMapper,或使用自定义 ResultSetExtractor 会禁用实体回调和生命周期事件,因为结果映射可以在需要时发出自己的事件/回调。 |