对于最新的稳定版本,请使用 Spring Data JPA 3.3.1Spring中文文档

对于最新的稳定版本,请使用 Spring Data JPA 3.3.1Spring中文文档

本节介绍使用 Spring Data JPA 创建查询的各种方法。Spring中文文档

查询查找策略

JPA 模块支持手动将查询定义为 String 或将其派生自方法名称。Spring中文文档

带有谓词 、 、 、 的派生查询将进行审查。 这意味着,如果参数实际上包含被通配符识别为通配符的字符,则这些字符将被转义,因此它们仅匹配为文本。 可以使用使用的转义字符来配置注释。 与使用 SpEL 表达式进行比较。IsStartingWithStartingWithStartsWithIsEndingWithEndingWithEndsWithIsNotContainingNotContainingNotContainsIsContainingContainingContainsLIKEescapeCharacter@EnableJpaRepositoriesSpring中文文档

声明的查询

尽管获取从方法名称派生的查询非常方便,但可能会面临方法名称解析器不支持要使用的关键字或方法名称变得不必要地丑陋的情况。因此,您可以通过命名约定使用 JPA 命名查询(有关更多信息,请参阅使用 JPA 命名查询),也可以使用(有关详细信息,请参阅使用 @Query)来注释查询方法。@QuerySpring中文文档

查询创建

通常,JPA 的查询创建机制的工作方式如查询方法中所述。以下示例显示了 JPA 查询方法转换为的内容:Spring中文文档

例 1.从方法名称创建查询
public interface UserRepository extends Repository<User, Long> {

  List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}

我们从中使用 JPA 条件 API 创建一个查询,但从本质上讲,这将转换为以下查询:.Spring Data JPA 执行属性检查并遍历嵌套属性,如属性表达式中所述。select u from User u where u.emailAddress = ?1 and u.lastname = ?2Spring中文文档

下表描述了 JPA 支持的关键字以及包含该关键字的方法的转换方式:Spring中文文档

表 1.方法名称中支持的关键字
关键词 样本 JPQL 片段

DistinctSpring中文文档

findDistinctByLastnameAndFirstnameSpring中文文档

select distinct …​ where x.lastname = ?1 and x.firstname = ?2Spring中文文档

AndSpring中文文档

findByLastnameAndFirstnameSpring中文文档

… where x.lastname = ?1 and x.firstname = ?2Spring中文文档

OrSpring中文文档

findByLastnameOrFirstnameSpring中文文档

… where x.lastname = ?1 or x.firstname = ?2Spring中文文档

Is,EqualsSpring中文文档

findByFirstname,,findByFirstnameIsfindByFirstnameEqualsSpring中文文档

… where x.firstname = ?1Spring中文文档

BetweenSpring中文文档

findByStartDateBetweenSpring中文文档

… where x.startDate between ?1 and ?2Spring中文文档

LessThanSpring中文文档

findByAgeLessThanSpring中文文档

… where x.age < ?1Spring中文文档

LessThanEqualSpring中文文档

findByAgeLessThanEqualSpring中文文档

… where x.age <= ?1Spring中文文档

GreaterThanSpring中文文档

findByAgeGreaterThanSpring中文文档

… where x.age > ?1Spring中文文档

GreaterThanEqualSpring中文文档

findByAgeGreaterThanEqualSpring中文文档

… where x.age >= ?1Spring中文文档

AfterSpring中文文档

findByStartDateAfterSpring中文文档

… where x.startDate > ?1Spring中文文档

BeforeSpring中文文档

findByStartDateBeforeSpring中文文档

… where x.startDate < ?1Spring中文文档

IsNull,NullSpring中文文档

findByAge(Is)NullSpring中文文档

… where x.age is nullSpring中文文档

IsNotNull,NotNullSpring中文文档

findByAge(Is)NotNullSpring中文文档

… where x.age is not nullSpring中文文档

LikeSpring中文文档

findByFirstnameLikeSpring中文文档

… where x.firstname like ?1Spring中文文档

NotLikeSpring中文文档

findByFirstnameNotLikeSpring中文文档

… where x.firstname not like ?1Spring中文文档

StartingWithSpring中文文档

findByFirstnameStartingWithSpring中文文档

… where x.firstname like ?1(附加参数绑定%)Spring中文文档

EndingWithSpring中文文档

findByFirstnameEndingWithSpring中文文档

… where x.firstname like ?1(参数绑定前置%)Spring中文文档

ContainingSpring中文文档

findByFirstnameContainingSpring中文文档

… where x.firstname like ?1(参数绑定包装在%)Spring中文文档

OrderBySpring中文文档

findByAgeOrderByLastnameDescSpring中文文档

… where x.age = ?1 order by x.lastname descSpring中文文档

NotSpring中文文档

findByLastnameNotSpring中文文档

… where x.lastname <> ?1Spring中文文档

InSpring中文文档

findByAgeIn(Collection<Age> ages)Spring中文文档

… where x.age in ?1Spring中文文档

NotInSpring中文文档

findByAgeNotIn(Collection<Age> ages)Spring中文文档

… where x.age not in ?1Spring中文文档

TrueSpring中文文档

findByActiveTrue()Spring中文文档

… where x.active = trueSpring中文文档

FalseSpring中文文档

findByActiveFalse()Spring中文文档

… where x.active = falseSpring中文文档

IgnoreCaseSpring中文文档

findByFirstnameIgnoreCaseSpring中文文档

… where UPPER(x.firstname) = UPPER(?1)Spring中文文档

In并且还可以将 的任何子类作为参数以及数组或 varargs。对于同一逻辑运算符的其他语法版本,请选中 Repository query keyryNotInCollection

DISTINCT可能很棘手,并不总是产生您期望的结果。 例如,将产生与 完全不同的结果。 在第一种情况下,由于您包含 ,因此不会有任何重复,因此您将获得整个表,并且它将是对象。select distinct u from User uselect distinct u.lastname from User uUser.idUserSpring中文文档

但是,后一种查询会将焦点缩小到仅查找该表的所有唯一姓氏。 这也将产生一个结果集,而不是一个结果集。User.lastnameList<String>List<User>Spring中文文档

countDistinctByLastname(String lastname)也会产生意想不到的结果。 Spring Data JPA 将派生 . 同样,由于不会命中任何重复项,因此此查询将计算具有绑定姓氏的所有用户。 这将与!select count(distinct u.id) from User u where u.lastname = ?1u.idcountByLastname(String lastname)Spring中文文档

无论如何,这个查询的意义何在?要找到具有给定姓氏的人数?要找到具有该绑定姓氏的不同人的数量? 要查找不同姓氏的数量?(最后一个是完全不同的查询! 使用有时需要手动编写查询,并用于最好地捕获您寻求的信息,因为您可能还需要投影 捕获结果集。distinct@QuerySpring中文文档

基于注释的配置

基于注释的配置具有不需要编辑其他配置文件的优点,从而降低了维护工作量。您需要为每个新的查询声明重新编译域类,从而为该权益付费。Spring中文文档

例 2.基于注释的命名查询配置
@Entity
@NamedQuery(name = "User.findByEmailAddress",
  query = "select u from User u where u.emailAddress = ?1")
public class User {

}
表 1.方法名称中支持的关键字
关键词 样本 JPQL 片段

DistinctSpring中文文档

findDistinctByLastnameAndFirstnameSpring中文文档

select distinct …​ where x.lastname = ?1 and x.firstname = ?2Spring中文文档

AndSpring中文文档

findByLastnameAndFirstnameSpring中文文档

… where x.lastname = ?1 and x.firstname = ?2Spring中文文档

OrSpring中文文档

findByLastnameOrFirstnameSpring中文文档

… where x.lastname = ?1 or x.firstname = ?2Spring中文文档

Is,EqualsSpring中文文档

findByFirstname,,findByFirstnameIsfindByFirstnameEqualsSpring中文文档

… where x.firstname = ?1Spring中文文档

BetweenSpring中文文档

findByStartDateBetweenSpring中文文档

… where x.startDate between ?1 and ?2Spring中文文档

LessThanSpring中文文档

findByAgeLessThanSpring中文文档

… where x.age < ?1Spring中文文档

LessThanEqualSpring中文文档

findByAgeLessThanEqualSpring中文文档

… where x.age <= ?1Spring中文文档

GreaterThanSpring中文文档

findByAgeGreaterThanSpring中文文档

… where x.age > ?1Spring中文文档

GreaterThanEqualSpring中文文档

findByAgeGreaterThanEqualSpring中文文档

… where x.age >= ?1Spring中文文档

AfterSpring中文文档

findByStartDateAfterSpring中文文档

… where x.startDate > ?1Spring中文文档

BeforeSpring中文文档

findByStartDateBeforeSpring中文文档

… where x.startDate < ?1Spring中文文档

IsNull,NullSpring中文文档

findByAge(Is)NullSpring中文文档

… where x.age is nullSpring中文文档

IsNotNull,NotNullSpring中文文档

findByAge(Is)NotNullSpring中文文档

… where x.age is not nullSpring中文文档

LikeSpring中文文档

findByFirstnameLikeSpring中文文档

… where x.firstname like ?1Spring中文文档

NotLikeSpring中文文档

findByFirstnameNotLikeSpring中文文档

… where x.firstname not like ?1Spring中文文档

StartingWithSpring中文文档

findByFirstnameStartingWithSpring中文文档

… where x.firstname like ?1(附加参数绑定%)Spring中文文档

EndingWithSpring中文文档

findByFirstnameEndingWithSpring中文文档

… where x.firstname like ?1(参数绑定前置%)Spring中文文档

ContainingSpring中文文档

findByFirstnameContainingSpring中文文档

… where x.firstname like ?1(参数绑定包装在%)Spring中文文档

OrderBySpring中文文档

findByAgeOrderByLastnameDescSpring中文文档

… where x.age = ?1 order by x.lastname descSpring中文文档

NotSpring中文文档

findByLastnameNotSpring中文文档

… where x.lastname <> ?1Spring中文文档

InSpring中文文档

findByAgeIn(Collection<Age> ages)Spring中文文档

… where x.age in ?1Spring中文文档

NotInSpring中文文档

findByAgeNotIn(Collection<Age> ages)Spring中文文档

… where x.age not in ?1Spring中文文档

TrueSpring中文文档

findByActiveTrue()Spring中文文档

… where x.active = trueSpring中文文档

FalseSpring中文文档

findByActiveFalse()Spring中文文档

… where x.active = falseSpring中文文档

IgnoreCaseSpring中文文档

findByFirstnameIgnoreCaseSpring中文文档

… where UPPER(x.firstname) = UPPER(?1)Spring中文文档

In并且还可以将 的任何子类作为参数以及数组或 varargs。对于同一逻辑运算符的其他语法版本,请选中 Repository query keyryNotInCollection

DISTINCT可能很棘手,并不总是产生您期望的结果。 例如,将产生与 完全不同的结果。 在第一种情况下,由于您包含 ,因此不会有任何重复,因此您将获得整个表,并且它将是对象。select distinct u from User uselect distinct u.lastname from User uUser.idUserSpring中文文档

但是,后一种查询会将焦点缩小到仅查找该表的所有唯一姓氏。 这也将产生一个结果集,而不是一个结果集。User.lastnameList<String>List<User>Spring中文文档

countDistinctByLastname(String lastname)也会产生意想不到的结果。 Spring Data JPA 将派生 . 同样,由于不会命中任何重复项,因此此查询将计算具有绑定姓氏的所有用户。 这将与!select count(distinct u.id) from User u where u.lastname = ?1u.idcountByLastname(String lastname)Spring中文文档

无论如何,这个查询的意义何在?要找到具有给定姓氏的人数?要找到具有该绑定姓氏的不同人的数量? 要查找不同姓氏的数量?(最后一个是完全不同的查询! 使用有时需要手动编写查询,并用于最好地捕获您寻求的信息,因为您可能还需要投影 捕获结果集。distinct@QuerySpring中文文档

使用 JPA 命名查询

这些示例使用元素和注释。这些配置元素的查询必须使用 JPA 查询语言进行定义。当然,您也可以使用或。通过这些元素,可以通过失去数据库平台独立性来在本机 SQL 中定义查询。<named-query />@NamedQuery<named-native-query />@NamedNativeQuery

XML 命名查询定义

要使用 XML 配置,请将必要的元素添加到位于类路径文件夹中的 JPA 配置文件中。命名查询的自动调用是通过使用一些定义的命名约定来实现的。有关详细信息,请参见下文。<named-query />orm.xmlMETA-INFSpring中文文档

例 3.XML 命名查询配置
<named-query name="User.findByLastname">
  <query>select u from User u where u.lastname = ?1</query>
</named-query>

查询具有用于在运行时解析查询的特殊名称。Spring中文文档

声明接口

若要允许这些命名查询,请指定如下:UserRepositorySpring中文文档

例 4.UserRepository 中的查询方法声明
public interface UserRepository extends JpaRepository<User, Long> {

  List<User> findByLastname(String lastname);

  User findByEmailAddress(String emailAddress);
}

Spring Data 尝试将对这些方法的调用解析为命名查询,从配置的域类的简单名称开始,然后是用点分隔的方法名称。 因此,前面的示例将使用前面定义的命名查询,而不是尝试从方法名称创建查询。Spring中文文档

这些示例使用元素和注释。这些配置元素的查询必须使用 JPA 查询语言进行定义。当然,您也可以使用或。通过这些元素,可以通过失去数据库平台独立性来在本机 SQL 中定义查询。<named-query />@NamedQuery<named-native-query />@NamedNativeQuery

@Query

使用命名查询来声明实体的查询是一种有效的方法,适用于少量查询。由于查询本身与运行它们的 Java 方法相关联,因此您实际上可以通过使用 Spring Data JPA 注释直接绑定它们,而不是将它们注释到域类。这将域类从特定于持久性的信息中释放出来,并将查询与存储库接口并置。@QuerySpring中文文档

批注到查询方法的查询优先于使用中声明的查询或命名查询。@NamedQueryorm.xmlSpring中文文档

以下示例显示了使用批注创建的查询:@QuerySpring中文文档

例 5.在查询方法中声明查询@Query
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.emailAddress = ?1")
  User findByEmailAddress(String emailAddress);
}

应用 QueryRewriter

有时,无论您尝试应用多少功能,似乎都不可能让 Spring Data JPA 应用所有功能 你希望在查询发送到 .EntityManagerSpring中文文档

您可以在查询被发送到 the 之前获取查询并“重写”它。那是 您可以在最后一刻进行任何更改。EntityManagerSpring中文文档

例 6.声明 QueryRewriter@Query
public interface MyRepository extends JpaRepository<User, Long> {

		@Query(value = "select original_user_alias.* from SD_USER original_user_alias",
                nativeQuery = true,
				queryRewriter = MyQueryRewriter.class)
		List<User> findByNativeQuery(String param);

		@Query(value = "select original_user_alias from User original_user_alias",
                queryRewriter = MyQueryRewriter.class)
		List<User> findByNonNativeQuery(String param);
}

此示例显示了本机(纯 SQL)重写器和 JPQL 查询,两者都利用相同的 . 在此方案中,Spring Data JPA 将查找在相应类型的应用程序上下文中注册的 Bean。QueryRewriterSpring中文文档

您可以像这样编写查询重写器:Spring中文文档

例 7.例QueryRewriter
public class MyQueryRewriter implements QueryRewriter {

     @Override
     public String rewrite(String query, Sort sort) {
         return query.replaceAll("original_user_alias", "rewritten_user_alias");
     }
}

你必须确保你在应用程序上下文中注册,无论是通过应用Spring Framework的基于注释的注解之一,还是将其作为类内方法的一部分。QueryRewriter@Component@Bean@ConfigurationSpring中文文档

另一种选择是让存储库本身实现接口。Spring中文文档

例 8.提供QueryRewriter
public interface MyRepository extends JpaRepository<User, Long>, QueryRewriter {

		@Query(value = "select original_user_alias.* from SD_USER original_user_alias",
                nativeQuery = true,
				queryRewriter = MyRepository.class)
		List<User> findByNativeQuery(String param);

		@Query(value = "select original_user_alias from User original_user_alias",
                queryRewriter = MyRepository.class)
		List<User> findByNonNativeQuery(String param);

		@Override
		default String rewrite(String query, Sort sort) {
			return query.replaceAll("original_user_alias", "rewritten_user_alias");
		}
}

根据您正在使用的内容,建议使用多个,每个都注册到 应用程序上下文。QueryRewriterSpring中文文档

在基于 CDI 的环境中,Spring Data JPA 将搜索 的实例。BeanManagerQueryRewriter

使用高级表达式LIKE

手动定义的查询的查询运行机制允许在查询定义中定义高级表达式,如以下示例所示:@QueryLIKESpring中文文档

例 9.@Query中的高级表达式like
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.firstname like %?1")
  List<User> findByFirstnameEndsWith(String firstname);
}

在前面的示例中,分隔符字符 () 被识别,并将查询转换为有效的 JPQL 查询(删除 )。运行查询时,传递给方法调用的参数将使用先前识别的模式进行扩充。LIKE%%LIKESpring中文文档

本机查询

注解允许通过将标志设置为 true 来运行本机查询,如以下示例所示:@QuerynativeQuerySpring中文文档

例 10.使用 @Query 在查询方法中声明本机查询
public interface UserRepository extends JpaRepository<User, Long> {

  @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
  User findByEmailAddress(String emailAddress);
}
Spring Data JPA 目前不支持对本机查询进行动态排序,因为它必须操作声明的实际查询,而对于本机 SQL 来说,它无法可靠地做到这一点。但是,可以通过自己指定计数查询来使用本机查询进行分页,如以下示例所示:
例 11.在查询方法中声明分页的本机计数查询,方法是使用@Query
public interface UserRepository extends JpaRepository<User, Long> {

  @Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
    countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
    nativeQuery = true)
  Page<User> findByLastname(String lastname, Pageable pageable);
}

类似的方法也适用于命名的本机查询,方法是将后缀添加到查询的副本中。不过,您可能需要为计数查询注册结果集映射。.countSpring中文文档

在基于 CDI 的环境中,Spring Data JPA 将搜索 的实例。BeanManagerQueryRewriter
Spring Data JPA 目前不支持对本机查询进行动态排序,因为它必须操作声明的实际查询,而对于本机 SQL 来说,它无法可靠地做到这一点。但是,可以通过自己指定计数查询来使用本机查询进行分页,如以下示例所示:

使用排序

可以通过提供或直接使用来完成排序。实例中实际使用的属性需要与域模型匹配,这意味着它们需要解析为查询中使用的属性或别名。JPQL 将其定义为状态字段路径表达式。PageRequestSortOrderSortSpring中文文档

使用任何不可引用的路径表达式都会导致 .Exception

但是,与 @Query 一起使用可以潜入包含子句中函数的非路径检查实例。这是可能的,因为 已追加到给定的查询字符串。默认情况下,Spring Data JPA 拒绝任何包含函数调用的实例,但您可以使用它来添加可能不安全的排序。SortOrderORDER BYOrderOrderJpaSort.unsafeSpring中文文档

以下示例使用 和 ,包括 unsafe 选项:SortJpaSortJpaSortSpring中文文档

例 12.使用 和SortJpaSort
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.lastname like ?1%")
  List<User> findByAndSort(String lastname, Sort sort);

  @Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%")
  List<Object[]> findByAsArrayAndSort(String lastname, Sort sort);
}

repo.findByAndSort("lannister", Sort.by("firstname"));                (1)
repo.findByAndSort("stark", Sort.by("LENGTH(firstname)"));            (2)
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); (3)
repo.findByAsArrayAndSort("bolton", Sort.by("fn_len"));               (4)
1 指向域模型中属性的有效表达式。Sort
2 包含函数调用无效。 引发异常。Sort
3 有效包含显式不安全的 .SortOrder
4 指向别名函数的有效表达式。Sort
使用任何不可引用的路径表达式都会导致 .Exception
1 指向域模型中属性的有效表达式。Sort
2 包含函数调用无效。 引发异常。Sort
3 有效包含显式不安全的 .SortOrder
4 指向别名函数的有效表达式。Sort

滚动大型查询结果

在处理大型数据集时,滚动有助于有效地处理这些结果,而无需将所有结果加载到内存中。Spring中文文档

您可以使用多个选项来使用大型查询结果:Spring中文文档

  1. 寻呼。 您在上一章中学习了 和 。PageablePageRequestSpring中文文档

  2. 基于偏移的滚动。 这是一个比分页更轻的变体,因为它不需要总结果计数。Spring中文文档

  3. 基于键集的滚动。 此方法通过利用数据库索引避免了基于偏移的结果检索的缺点Spring中文文档

阅读更多关于哪种方法最适合您的特定安排的信息。Spring中文文档

可以将 Scroll API 与查询方法、Query-by-ExampleQuerydsl 一起使用。Spring中文文档

尚不支持使用基于字符串的查询方法进行滚动。 也不支持使用存储查询方法进行滚动。@Procedure
尚不支持使用基于字符串的查询方法进行滚动。 也不支持使用存储查询方法进行滚动。@Procedure

使用命名参数

默认情况下,Spring Data JPA 使用基于位置的参数绑定,如前面所有示例中所述。 这使得查询方法在重构参数位置时容易出错。 若要解决此问题,可以使用批注为方法参数指定具体名称,并在查询中绑定该名称,如以下示例所示:@ParamSpring中文文档

例 13.使用命名参数
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
  User findByLastnameOrFirstname(@Param("lastname") String lastname,
                                 @Param("firstname") String firstname);
}
方法参数根据它们在定义的查询中的顺序进行切换。
从版本 4 开始,Spring 完全支持基于编译器标志的 Java 8 参数名称发现。通过在生成中使用此标志作为调试信息的替代方法,可以省略命名参数的批注。-parameters@Param
方法参数根据它们在定义的查询中的顺序进行切换。
从版本 4 开始,Spring 完全支持基于编译器标志的 Java 8 参数名称发现。通过在生成中使用此标志作为调试信息的替代方法,可以省略命名参数的批注。-parameters@Param

使用 SpEL 表达式

从 Spring Data JPA 1.4 版开始,我们支持在手动定义的查询中使用受限制的 SpEL 模板表达式,这些查询是用 定义的。运行查询后,将根据一组预定义的变量计算这些表达式。Spring Data JPA 支持一个名为 的变量。它的用法是 。它插入与给定存储库关联的域类型。解析方式如下:如果域类型在注释上设置了 name 属性,则使用该属性。否则,将使用域类型的简单类名。@QueryentityNameselect x from #{#entityName} xentityNameentityName@EntitySpring中文文档

以下示例演示了查询字符串中表达式的一个用例,其中您希望使用查询方法和手动定义的查询来定义存储库接口:#{#entityName}Spring中文文档

例 14.在存储库查询方法中使用 SpEL 表达式 - entityName
@Entity
public class User {

  @Id
  @GeneratedValue
  Long id;

  String lastname;
}

public interface UserRepository extends JpaRepository<User,Long> {

  @Query("select u from #{#entityName} u where u.lastname = ?1")
  List<User> findByLastname(String lastname);
}

若要避免在批注的查询字符串中声明实际实体名称,可以使用该变量。@Query#{#entityName}Spring中文文档

可以使用注释进行自定义。SpEL 表达式不支持自定义项。entityName@Entityorm.xml

当然,您可以直接在查询声明中使用,但这还需要更改查询。对 的引用将选取类将来可能重新映射到不同的实体名称(例如,通过使用 .User#entityNameUser@Entity(name = "MyUser")Spring中文文档

查询字符串中表达式的另一个用例是,如果要为具体域类型定义具有专用存储库接口的通用存储库接口。为了不重复具体接口上自定义查询方法的定义,可以在通用存储库接口的注解的查询字符串中使用实体名称表达式,如以下示例所示:#{#entityName}@QuerySpring中文文档

例 15.在存储库查询方法中使用 SpEL 表达式 - 具有继承功能的 entityName
@MappedSuperclass
public abstract class AbstractMappedType {
  …
  String attribute;
}

@Entity
public class ConcreteType extends AbstractMappedType { … }

@NoRepositoryBean
public interface MappedTypeRepository<T extends AbstractMappedType>
  extends Repository<T, Long> {

  @Query("select t from #{#entityName} t where t.attribute = ?1")
  List<T> findAllByAttribute(String attribute);
}

public interface ConcreteRepository
  extends MappedTypeRepository<ConcreteType> { … }

在前面的示例中,接口是几个域类型扩展的通用父接口。它还定义了泛型方法,该方法可用于专用存储库接口的实例。如果现在调用 ,则查询变为 。MappedTypeRepositoryAbstractMappedTypefindAllByAttribute(…)findByAllAttribute(…)ConcreteRepositoryselect t from ConcreteType t where t.attribute = ?1Spring中文文档

用于操作参数的 SpEL 表达式也可用于操作方法参数。 在这些 SpEL 表达式中,实体名称不可用,但参数可用。 可以按名称或索引访问它们,如以下示例所示。Spring中文文档

例 16.在存储库查询方法中使用 SpEL 表达式 - 访问参数。
@Query("select u from User u where u.firstname = ?1 and u.firstname=?#{[0]} and u.emailAddress = ?#{principal.emailAddress}")
List<User> findByFirstnameAndCurrentUserWithCustomQuery(String firstname);

对于 -conditions,人们通常希望附加到 String 值参数的开头或结尾。 这可以通过在绑定参数标记或 SpEL 表达式后面加上 . 下面的示例再次演示了这一点。like%%Spring中文文档

例 17.在存储库查询方法中使用 SpEL 表达式 - 通配符快捷方式。
@Query("select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%")
List<User> findByLastnameWithSpelExpression(@Param("lastname") String lastname);

当将 -conditions 与来自不安全源的值一起使用时,应清理这些值,以便它们不能包含任何通配符,从而允许攻击者选择比他们应该能够选择的更多的数据。 为此,该方法在 SpEL 上下文中可用。 它用第二个参数中的单个字符作为第一个参数中 and 的所有实例的前缀。 结合 JPQL 和标准 SQL 中可用的表达式子句,这允许轻松清理绑定参数。likeescape(String)_%escapelikeSpring中文文档

例 18.在存储库查询方法中使用 SpEL 表达式 - 清理输入值。
@Query("select u from User u where u.firstname like %?#{escape([0])}% escape ?#{escapeCharacter()}")
List<User> findContainingEscaped(String namePart);

给定此方法,存储库接口中的声明将找到但不会。 可以使用使用的转义字符来配置注释。 请注意,SpEL 上下文中可用的方法只会转义 SQL 和 JPQL 标准通配符和 . 如果基础数据库或 JPA 实现支持其他通配符,则不会对这些通配符进行转义。findContainingEscaped("Peter_")Peter_ParkerPeter ParkerescapeCharacter@EnableJpaRepositoriesescape(String)_%Spring中文文档

可以使用注释进行自定义。SpEL 表达式不支持自定义项。entityName@Entityorm.xml

其他方法

Spring Data JPA 提供了许多构建查询的方法。 但有时,您的查询对于所提供的技术来说可能太复杂了。 在这种情况下,请考虑:Spring中文文档

当您需要最大程度地控制查询,同时仍然让 Spring Data JPA 提供资源管理时,这些策略可能是最有效的。Spring中文文档

修改查询

前面的所有部分都介绍了如何声明查询以访问给定实体或实体集合。 您可以使用 Spring 数据存储库的自定义实现中描述的自定义方法工具添加自定义修改行为。 由于此方法对于全面的自定义功能是可行的,因此可以通过使用 批注查询方法来修改只需要参数绑定的查询,如以下示例所示:@ModifyingSpring中文文档

例 19.声明操作查询
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);

这样做会触发批注到方法的查询作为更新查询,而不是选择查询。由于在执行修改查询后可能包含过时的实体,因此我们不会自动清除它(有关详细信息,请参阅 JavaDoc),因为这实际上会删除 . 如果您希望自动清除,可以将注释的属性设置为 。EntityManagerEntityManager.clear()EntityManagerEntityManager@ModifyingclearAutomaticallytrueSpring中文文档

注释仅与注释结合使用。 派生查询方法或自定义方法不需要此批注。@Modifying@QuerySpring中文文档

派生的删除查询

Spring Data JPA 还支持派生的删除查询,让您不必显式声明 JPQL 查询,如以下示例所示:Spring中文文档

例 20.使用派生删除查询
interface UserRepository extends Repository<User, Long> {

  void deleteByRoleId(long roleId);

  @Modifying
  @Query("delete from User u where u.role.id = ?1")
  void deleteInBulkByRoleId(long roleId);
}

尽管该方法看起来基本上生成与 相同的结果,但就它们的运行方式而言,这两个方法声明之间存在重要差异。 顾名思义,后一种方法对数据库发出单个 JPQL 查询(在注释中定义的查询)。 这意味着即使当前加载的实例也看不到调用的生命周期回调。deleteByRoleId(…)deleteInBulkByRoleId(…)UserSpring中文文档

为了确保实际调用生命周期查询,调用将运行查询,然后逐个删除返回的实例,以便持久性提供程序可以实际调用这些实体的回调。deleteByRoleId(…)@PreRemoveSpring中文文档

实际上,派生删除查询是运行查询然后调用结果并使行为与 中其他方法的实现保持同步的快捷方式。CrudRepository.delete(Iterable<User> users)delete(…)CrudRepositorySpring中文文档

应用查询提示

要将 JPA 查询提示应用于存储库界面中声明的查询,可以使用注释。它需要 JPA 注释数组和一个布尔标志,以潜在地禁用应用于应用分页时触发的其他计数查询的提示,如以下示例所示:@QueryHints@QueryHintSpring中文文档

例 21.将 QueryHints 与存储库方法结合使用
public interface UserRepository extends Repository<User, Long> {

  @QueryHints(value = { @QueryHint(name = "name", value = "value")},
              forCounting = false)
  Page<User> findByLastname(String lastname, Pageable pageable);
}

前面的声明将应用为该实际查询配置的声明,但省略将其应用于为计算总页数而触发的计数查询。@QueryHintSpring中文文档

向查询添加注释

有时,您需要根据数据库性能调试查询。 数据库管理员显示的查询可能看起来与您编写的查询大不相同,或者它可能看起来 与您认为的Spring Data JPA生成的有关自定义查找器的内容完全不同,或者如果您通过示例使用查询。@QuerySpring中文文档

为了简化此过程,您可以将自定义注释插入到几乎任何 JPA 操作中,无论是查询还是其他操作 通过应用注释。@MetaSpring中文文档

例 22.将批注应用于存储库操作@Meta
public interface RoleRepository extends JpaRepository<Role, Integer> {

	@Meta(comment = "find roles by name")
	List<Role> findByName(String name);

	@Override
	@Meta(comment = "find roles using QBE")
	<S extends Role> List<S> findAll(Example<S> example);

	@Meta(comment = "count roles for a given name")
	long countByName(String name);

	@Override
	@Meta(comment = "exists based on QBE")
	<S extends Role> boolean exists(Example<S> example);
}

此示例存储库混合了自定义查找器,并覆盖了从 继承的操作。 无论采用哪种方式,注释都允许您添加一个将在查询发送到数据库之前插入到查询中的内容。JpaRepository@MetacommentSpring中文文档

同样重要的是要注意,此功能不仅限于查询。它扩展到 and 操作。 虽然没有显示,但它也扩展到某些操作。countexistsdeleteSpring中文文档

虽然我们已尝试在可能的情况下应用此功能,但底层的某些操作不支持注释。例如,被清楚地记录为支持性注释,但操作没有。EntityManagerentityManager.createQuery()entityManager.find()

JPQL 日志记录和 SQL 日志记录都不是 JPA 中的标准,因此每个提供程序都需要自定义配置,如下图所示。Spring中文文档

激活 Hibernate 注释

要在 Hibernate 中激活查询注释,必须设置为 。hibernate.use_sql_commentstrueSpring中文文档

如果您使用的是基于 Java 的配置设置,可以按以下方式完成:Spring中文文档

例 23.基于 Java 的 JPA 配置
@Bean
public Properties jpaProperties() {

	Properties properties = new Properties();
	properties.setProperty("hibernate.use_sql_comments", "true");
	return properties;
}

如果您有文件,则可以在此处应用它:persistence.xmlSpring中文文档

例 24.-基于配置persistence.xml
<persistence-unit name="my-persistence-unit">

   ...registered classes...

	<properties>
		<property name="hibernate.use_sql_comments" value="true" />
	</properties>
</persistence-unit>

最后,如果您使用的是 Spring Boot,则可以在文件中设置它:application.propertiesSpring中文文档

例 25.Spring Boot 基于属性的配置
spring.jpa.properties.hibernate.use_sql_comments=true

要在 EclipseLink 中激活查询注释,必须设置为 。eclipselink.logging.level.sqlFINESpring中文文档

如果您使用的是基于 Java 的配置设置,可以按以下方式完成:Spring中文文档

例 26.基于 Java 的 JPA 配置
@Bean
public Properties jpaProperties() {

	Properties properties = new Properties();
	properties.setProperty("eclipselink.logging.level.sql", "FINE");
	return properties;
}

如果您有文件,则可以在此处应用它:persistence.xmlSpring中文文档

例 27.-基于配置persistence.xml
<persistence-unit name="my-persistence-unit">

   ...registered classes...

	<properties>
		<property name="eclipselink.logging.level.sql" value="FINE" />
	</properties>
</persistence-unit>

最后,如果您使用的是 Spring Boot,则可以在文件中设置它:application.propertiesSpring中文文档

例 28.Spring Boot 基于属性的配置
spring.jpa.properties.eclipselink.logging.level.sql=FINE
虽然我们已尝试在可能的情况下应用此功能,但底层的某些操作不支持注释。例如,被清楚地记录为支持性注释,但操作没有。EntityManagerentityManager.createQuery()entityManager.find()

配置 Fetch- 和 LoadGraphs

JPA 2.1 规范引入了对指定 Fetch- 和 LoadGraphs 的支持,我们也支持这些 Fetch- 和 LoadGraphs 的注释,它允许您引用定义。您可以在实体上使用该注释来配置生成的查询的提取计划。提取的类型(或)可以通过使用注释上的属性来配置。请参阅 JPA 2.1 规范 3.7.4 以获取进一步的参考。@EntityGraph@NamedEntityGraphFetchLoadtype@EntityGraphSpring中文文档

下面的示例演示如何在实体上定义命名实体图:Spring中文文档

例 29.在实体上定义命名实体图。
@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
  attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {

  // default fetch mode is lazy.
  @ManyToMany
  List<GroupMember> members = new ArrayList<GroupMember>();

  …
}

下面的示例演示如何在存储库查询方法上引用命名实体图:Spring中文文档

例 30.在存储库查询方法上引用命名实体图定义。
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

  @EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
  GroupInfo getByGroupName(String name);

}

也可以使用 定义临时实体图。提供的将转换为 according 属性,而无需显式添加到域类型中,如以下示例所示:@EntityGraphattributePathsEntityGraph@NamedEntityGraphSpring中文文档

例 31.在存储库查询方法上使用 AD-HOC 实体图定义。
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

  @EntityGraph(attributePaths = { "members" })
  GroupInfo getByGroupName(String name);

}

滚动

滚动是一种更细粒度的方法,用于循环访问更大的结果集块。 滚动包括稳定排序、滚动类型(基于偏移或键集的滚动)和结果限制。 可以使用属性名称定义简单的排序表达式,并通过查询派生使用 TopFirst 关键字定义静态结果限制。 您可以连接表达式以将多个条件收集到一个表达式中。Spring中文文档

滚动查询返回一个,该查询允许获取滚动位置以继续获取下一个滚动位置,直到应用程序使用完整个查询结果。 与通过获取下一批结果来使用 Java 类似,查询结果滚动允许您访问 a 到 .Window<T>Window<T>Iterator<List<…>>ScrollPositionWindow.positionAt(…​)Spring中文文档

Window<User> users = repository.findFirst10ByLastnameOrderByFirstname("Doe", ScrollPosition.offset());
do {

  for (User u : users) {
    // consume the user
  }

  // obtain the next Scroll
  users = repository.findFirst10ByLastnameOrderByFirstname("Doe", users.positionAt(users.size() - 1));
} while (!users.isEmpty() && users.hasNext());

WindowIterator提供了一个实用程序,通过消除检查是否存在下一个并应用 .WindowWindowScrollPositionSpring中文文档

WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
  .startingAt(OffsetScrollPosition.initial());

while (users.hasNext()) {
  User u = users.next();
  // consume the user
}

使用偏移量滚动

偏移滚动使用类似于分页的偏移计数器来跳过多个结果,并让数据源仅返回从给定偏移量开始的结果。 这种简单的机制可避免将大量结果发送到客户端应用程序。 但是,大多数数据库需要具体化完整的查询结果,然后服务器才能返回结果。Spring中文文档

例 32.与存储库查询方法一起使用OffsetScrollPosition
interface UserRepository extends Repository<User, Long> {

  Window<User> findFirst10ByLastnameOrderByFirstname(String lastname, OffsetScrollPosition position);
}

WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
  .startingAt(OffsetScrollPosition.initial()); (1)
1 从位置的初始偏移量开始。0

使用键集筛选进行滚动

基于偏移量要求大多数数据库需要具体化整个结果,然后服务器才能返回结果。 因此,虽然客户端只能看到请求结果的部分,但您的服务器需要构建完整的结果,这会导致额外的负载。Spring中文文档

Keyset-Filtering 通过利用数据库的内置功能来检索结果子集,旨在减少单个查询的计算和 I/O 要求。 此方法维护一组键,通过将键传递到查询中来恢复滚动,从而有效地修改筛选条件。Spring中文文档

Keyset-Filtering 的核心思想是使用稳定的排序顺序开始检索结果。 一旦要滚动到下一个块,您将获得一个用于重建排序结果中的位置。 捕获当前 . 若要运行查询,重建将重写 criteria 子句以包含所有排序字段和主键,以便数据库可以利用潜在索引来运行查询。 数据库只需要从给定的键集位置构造一个小得多的结果,而不需要完全实现一个大的结果,然后跳过结果直到达到特定的偏移量。ScrollPositionScrollPositionWindowSpring中文文档

键集筛选要求键集属性(用于排序的属性)为非空值。 由于比较运算符的存储特定值处理以及需要对索引源运行查询,因此适用此限制。 对可为 null 的属性进行键集筛选将导致意外结果。nullSpring中文文档

与存储库查询方法一起使用KeysetScrollPosition
interface UserRepository extends Repository<User, Long> {

  Window<User> findFirst10ByLastnameOrderByFirstname(String lastname, KeysetScrollPosition position);
}

WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
  .startingAt(ScrollPosition.keyset()); (1)
1 从头开始,不要应用额外的筛选。

当数据库包含与排序字段匹配的索引时,键集筛选效果最佳,因此静态排序效果很好。 应用键集筛选的滚动查询需要查询返回的排序顺序中使用的属性,并且这些属性必须映射到返回的实体中。Spring中文文档

您可以使用接口和 DTO 投影,但请确保包含已排序的所有属性,以避免键集提取失败。Spring中文文档

指定订单时,包含与查询相关的排序属性就足够了; 如果不想,则无需确保查询结果的唯一性。 键集查询机制通过包含主键(或复合主键的任何剩余部分)来修改排序顺序,以确保每个查询结果都是唯一的。SortSpring中文文档

1 从位置的初始偏移量开始。0

键集筛选要求键集属性(用于排序的属性)为非空值。 由于比较运算符的存储特定值处理以及需要对索引源运行查询,因此适用此限制。 对可为 null 的属性进行键集筛选将导致意外结果。nullSpring中文文档

1 从头开始,不要应用额外的筛选。