本节提供有关 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 该方法显示对具有给定 . 通过分析可与 和 连接的约束的方法名称来派生查询。 因此,方法名称将生成 .firstnameAndOrSELECT … FROM person WHERE firstname = :firstname
2 用于将偏移量和排序参数传递到数据库。Pageable
3 返回 .选择行以确定是否有更多数据可供使用。 不支持自定义。Slice<Person>LIMIT+1ResultSetExtractor
4 运行分页查询,返回 。仅选择给定页面边界内的数据,并可能选择计数查询以确定总计数。 不支持自定义。Page<Person>ResultSetExtractor
5 查找给定条件的单个实体。 它以非唯一结果完成。IncorrectResultSizeDataAccessException
6 与 <3> 相比,即使查询生成更多结果文档,也始终发出第一个实体。
7 该方法显示对具有给定 .findByLastnamelastname
8 该方法返回一个 ,这使得值在从数据库返回后立即成为可能。streamByLastnameStream
9 您可以使用 Spring Expression Language 动态解析参数。 在此示例中,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, ,StartingWithEndingWith

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

(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
1 该方法显示对具有给定 . 通过分析可与 和 连接的约束的方法名称来派生查询。 因此,方法名称将生成 .firstnameAndOrSELECT … FROM person WHERE firstname = :firstname
2 用于将偏移量和排序参数传递到数据库。Pageable
3 返回 .选择行以确定是否有更多数据可供使用。 不支持自定义。Slice<Person>LIMIT+1ResultSetExtractor
4 运行分页查询,返回 。仅选择给定页面边界内的数据,并可能选择计数查询以确定总计数。 不支持自定义。Page<Person>ResultSetExtractor
5 查找给定条件的单个实体。 它以非唯一结果完成。IncorrectResultSizeDataAccessException
6 与 <3> 相比,即使查询生成更多结果文档,也始终发出第一个实体。
7 该方法显示对具有给定 .findByLastnamelastname
8 该方法返回一个 ,这使得值在从数据库返回后立即成为可能。streamByLastnameStream
9 您可以使用 Spring Expression Language 动态解析参数。 在此示例中,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, ,StartingWithEndingWith

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

(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

查询查找策略

JDBC 模块支持手动将查询定义为注释中的 String 或属性文件中的命名查询。@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 自身生成的查询相同的方法。 您提供的查询必须与预期的格式匹配。 必须提供实体构造函数中使用的所有属性的列。 通过 setter、wither 或字段访问设置的属性的列是可选的。 不会设置结果中没有匹配列的属性。 该查询用于填充聚合根、嵌入实体和一对一关系,包括作为 SQL 数组类型存储和加载的基元类型的数组。 为实体的地图、列表、集合和数组生成单独的查询。RowMapperRowMapper

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

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

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

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

Spring 完全支持 Java 8 基于编译器标志的参数名称发现。 通过在生成中使用此标志作为调试信息的替代方法,可以省略命名参数的批注。-parameters@Param
Spring Data JDBC 仅支持命名参数。
请注意,基于字符串的查询不支持分页,也不接受 、 ,并且作为查询参数,对于这些查询,需要重写查询。 如果要应用限制,请使用 SQL 表达此意图,并自行将相应的参数绑定到查询。SortPageRequestLimit
Spring 完全支持 Java 8 基于编译器标志的参数名称发现。 通过在生成中使用此标志作为调试信息的替代方法,可以省略命名参数的批注。-parameters@Param
Spring Data JDBC 仅支持命名参数。

命名查询

如果注释中没有给出查询,如上一节所述,Spring Data JDBC 将尝试查找命名查询。 有两种方法可以确定查询的名称。 默认设置是采用查询的域类,即存储库的聚合根目录,采用其简单名称,并附加用 分隔的方法名称。 或者,批注具有一个属性,该属性可用于指定要查找的查询的名称。.@Queryname

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

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

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

自定义查询方法

流式处理结果

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

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

自定义或RowMapperResultSetExtractor

注释允许您指定自定义或使用。 属性,并允许您指定要使用的类,这些类将使用默认构造函数进行实例化。 或者,您可以从 Spring 应用程序上下文中设置 或 Bean 名称。@QueryRowMapperResultSetExtractorrowMapperClassresultSetExtractorClassrowMapperClassRefresultSetExtractorClassRef

如果不仅要对单个方法使用某个方法,而且要对具有返回特定类型的自定义查询的所有方法使用特定方法, 您可以注册一个 Bean 并注册一个 Per Method 返回类型。 以下示例演示如何注册:RowMapperRowMapperMapRowMapperDefaultQueryMappingConfiguration

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

在确定方法使用哪个方法时,根据方法的返回类型,将遵循以下步骤:RowMapper

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

    相反,查询应返回具有单个列的单行,并将返回类型的转换应用于该值。

  2. 将迭代 中的实体类,直到找到一个是相关返回类型的超类或接口。 使用该类的注册。QueryMappingConfigurationRowMapper

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

如果适用,包装类型(如集合或已解包)。 因此,返回类型 使用前面进程中的类型。OptionalOptional<Person>Person

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

修改查询

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

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

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

  • void

  • int(更新的记录计数)

  • boolean(记录是否已更新)

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

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