查询方法

本节提供有关 Spring Data JDBC 的实现和使用的一些具体信息。

您通常在存储库上触发的多数数据访问操作都会导致对数据库运行查询。定义此类查询是通过在存储库接口上声明方法来实现的,如下面的示例所示

带有查询方法的 PersonRepository
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 的人员的查询。通过解析方法名称以查找可以与 AndOr 连接的约束来推导出查询。因此,方法名称会生成 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 表达式语言动态解析参数。在示例中,Spring Security 用于解析当前用户的用户名。

下表显示了查询方法支持的关键字。

表 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 为 FALSE

查询推导仅限于可以在 WHERE 子句中使用而无需使用联接的属性。

查询查找策略

JDBC 模块支持在 @Query 注解中手动定义查询作为字符串,或在属性文件中定义为命名查询。

从方法名称推导出查询目前仅限于简单属性,这意味着直接存在于聚合根中的属性。此外,这种方法仅支持选择查询。

使用 @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);
}

为了将查询结果转换为实体,默认情况下使用与 Spring Data JDBC 自身生成的查询相同的 RowMapper。您提供的查询必须与 RowMapper 预期的格式匹配。必须提供用于实体构造函数中的所有属性的列。用于通过 setter 或字段访问设置的属性的列是可选的。没有在结果中匹配列的属性将不会被设置。该查询用于填充聚合根、嵌入式实体和一对一关系,包括存储和加载为 SQL 数组类型的原始类型数组。将为实体的映射、列表、集合和数组生成单独的查询。

请注意,基于字符串的查询不支持分页,也不接受 SortPageRequestLimit 作为查询参数,因为对于这些查询,需要重写查询。如果您想应用限制,请使用 SQL 表达此意图,并自行将适当的参数绑定到查询。

查询可能包含允许绑定变量的 SpEL 表达式。这样的 SpEL 表达式将被替换为绑定变量,并且该变量将绑定到 SpEL 表达式的结果。

在查询中使用 SpEL
@Query("SELECT * FROM person WHERE id = :#{#person.id}")
Person findWithSpEL(PersonRef person);

这可以用于访问参数的成员,如上面的示例所示。对于更复杂的用例,可以在应用程序上下文中提供 EvaluationContextExtension,它反过来可以将任何对象提供给 SpEL。

Spring 完全支持 Java 8 基于 `-parameters` 编译器标志的参数名称发现。通过在构建中使用此标志作为调试信息的替代方案,您可以省略用于命名参数的 `@Param` 注解。
Spring Data JDBC 仅支持命名参数。

命名查询

如果在上一节中描述的注释中没有给出查询,Spring Data JDBC 将尝试定位命名查询。确定查询名称有两种方法。默认方法是获取查询的域类,即存储库的聚合根,获取其简单名称,并用 `.` 将其与方法名称连接起来。或者,`@Query` 注解有一个 `name` 属性,可用于指定要查找的查询的名称。

命名查询应在类路径上的属性文件 `META-INF/jdbc-named-queries.properties` 中提供。

可以通过将值设置为 `@EnableJdbcRepositories.namedQueriesLocation` 来更改该文件的路径。

命名查询的处理方式与注释提供的查询相同。

自定义查询方法

流式结果

当您将 Stream 指定为查询方法的返回类型时,Spring Data JDBC 会在元素可用时立即返回它们。在处理大量数据时,这适合于减少延迟和内存需求。

该流包含一个到数据库的开放连接。为了避免内存泄漏,最终需要通过关闭流来关闭该连接。推荐的方法是使用 `try-with-resource` 语句。这也意味着,一旦与数据库的连接关闭,流就无法获取更多元素,并且可能会抛出异常。

自定义 `RowMapper` 或 `ResultSetExtractor`

@Query 注解允许您指定要使用的自定义 RowMapperResultSetExtractor。属性 rowMapperClassresultSetExtractorClass 允许您指定要使用的类,这些类将使用默认构造函数实例化。或者,您可以将 rowMapperClassRefresultSetExtractorClassRef 设置为来自 Spring 应用程序上下文的 bean 名称。

如果您希望使用某个 RowMapper 不仅用于单个方法,而是用于所有返回特定类型自定义查询的方法,您可以注册一个 RowMapperMap bean 并为每个方法返回类型注册一个 RowMapper。以下示例展示了如何注册 DefaultQueryMappingConfiguration

@Bean
QueryMappingConfiguration rowMappers() {
  return new DefaultQueryMappingConfiguration()
    .register(Person.class, new PersonRowMapper())
    .register(Address.class, new AddressRowMapper());
}

在确定要为方法使用哪个 RowMapper 时,将根据方法的返回类型执行以下步骤

  1. 如果类型是简单类型,则不使用 RowMapper

    相反,查询预计将返回包含单个列的单个行,并将对该值应用转换为返回类型。

  2. 将迭代 QueryMappingConfiguration 中的实体类,直到找到一个作为所讨论返回类型的超类或接口的实体类。将使用为该类注册的 RowMapper

    迭代按注册顺序进行,因此请确保在特定类型之后注册更通用的类型。

如果适用,将解开包装类型,例如集合或 Optional。因此,Optional<Person> 的返回类型在前面的过程中使用 Person 类型。

通过 QueryMappingConfiguration@Query(rowMapperClass=…) 或自定义 ResultSetExtractor 使用自定义 RowMapper 将禁用实体回调和生命周期事件,因为结果映射可以根据需要发出自己的事件/回调。

修改查询

您可以通过在查询方法上使用 @Modifying 来将查询标记为修改查询,如下面的示例所示

@Modifying
@Query("UPDATE DUMMYENTITY SET name = :name WHERE id = :id")
boolean updateName(@Param("id") Long id, @Param("name") String name);

您可以指定以下返回类型

  • void

  • int(更新的记录数)

  • boolean(是否更新了记录)

修改查询直接针对数据库执行。不会调用任何事件或回调。因此,如果在带注释的查询中没有更新具有审计注释的字段,则这些字段也不会更新。