Query Methods

Spring Data JDBC 简化了基于 JDBC 的数据库访问,提供了声明式查询方法,支持分页、排序、流式处理和自定义查询。它从方法名称派生查询,解析约束并将其转换为 WHERE 子句,并允许使用注解和属性文件进行查询自定义。此外,它提供了流式结果和自定义行映射器等高级功能,以优化性能和灵活性。

本节提供了有关 Spring Data JDBC 的实现和使用的一些具体信息。 大多数您通常在存储库上触发的数据库访问操作会导致针对数据库运行查询。定义此类查询是声明存储库接口方法的问题,如下例所示:

PersonRepository with query methods
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 用于解析当前用户的用户名。

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

Table 1. Supported keywords for query methods
Keyword Sample Logical result

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 + '%'

(No keyword)

findByFirstname(String name)

firstname = name

Not

findByFirstnameNot(String name)

firstname != name

IsTrue, True

findByActiveIsTrue()

active IS TRUE

IsFalse, False

findByActiveIsFalse()

active IS FALSE

查询派生仅限于可以在 WHERE 子句中使用的属性,而不使用联接。

Query Lookup Strategies

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

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

Using @Query

以下示例展示如何使用 @Query 声明查询方法:

Declare a query method by using @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 数组类型的基本类型数组。为实体映射、列表、集合和数组生成了单独的查询。

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

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

Use a SpEL in a query
@Query("SELECT * FROM person WHERE id = :#{person.id}")
Person findWithSpEL(PersonRef person);

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

Spring 完全支持基于 -parameters 编译器标志的 Java 8 的参数名称发现。在您的构建中将这个标志作为调试信息的替代,您可以省略带命名参数的 @Param 注释。

Spring Data JDBC 仅支持命名参数。

Named Queries

如果没有按照上一部分的说明在注释中提供查询,Spring Data JDBC 将尝试查找一个命名查询。有两种方法可确定查询名称。默认做法是获取查询的 域类,即存储库的聚合根,获取其简单名称并附加方法名称,用“.”分隔。或者,@Query 注释具有 name 属性,可用于指定要查找的查询的名称。

命名查询应该提供给 classpath 中的属性文件 META-INF/jdbc-named-queries.properties

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

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

Customizing Query Methods

Streaming Results

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

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

Custom RowMapper or 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 Query

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

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

您可以指定以下返回类型:

  • void

  • int (updated record count)

  • boolean(是否已更新记录)

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