查询方法

本节提供了一些关于 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` 的人员的查询。该查询是通过解析方法名称来推导的,该名称中的约束条件可以用 `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`,该 `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` 用于字符串

findByFirstnameContaining(String name)

firstname LIKE '%' + name + '%'

`NotContaining` 用于字符串

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

查询派生仅限于可在 `WHERE` 子句中使用而无需使用连接的属性。

查询查找策略

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

从方法名称派生查询目前仅限于简单的属性,这意味着直接存在于聚合根中的属性。此外,此方法仅支持 select 查询。

使用 `@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 数组类型存储和加载的原始类型数组。将为实体的映射、列表、集合和数组生成单独的查询。

一对一关系的属性必须在其名称前加上关系名称和_。例如,如果上面示例中的User有一个address,其属性为city,则该city的列必须标记为address_city

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

查询可能包含SpEL表达式。有两种变体,其评估方式不同。

在第一种变体中,SpEL表达式以:为前缀,并像绑定变量一样使用。此类SpEL表达式将被绑定变量替换,并且该变量将绑定到SpEL表达式的结果。

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

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

另一种变体可以在查询中的任何位置使用,并且评估查询的结果将替换查询字符串中的表达式。

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

它在第一次执行之前进行一次评估,并使用StandardEvaluationContext,其中添加了两个变量tableNamequalifiedTableName。当表名本身是动态的时,这种用法最有用,因为它们也使用SpEL表达式。

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

命名查询

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

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

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

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

自定义查询方法

流式结果

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

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

自定义RowMapperResultSetExtractor

@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(是否更新了记录)

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