此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Data JPA 3.5.2spring-doc.cadn.net.cn

JPA 查询方法

本节介绍使用 Spring Data JPA 创建查询的各种方法。spring-doc.cadn.net.cn

查询查找策略

JPA 模块支持手动将查询定义为 String 或从方法名称派生它。spring-doc.cadn.net.cn

使用谓词的派生查询IsStartingWith,StartingWith,StartsWith,IsEndingWith,EndingWith,EndsWith,IsNotContaining,NotContaining,NotContains,IsContaining,Containing,Contains这些查询的相应参数将被清理。 这意味着如果参数实际上包含由LIKE作为通配符,这些将被转义,因此它们仅作为文字匹配。 可以使用的转义字符通过设置escapeCharacter@EnableJpaRepositories注解。 与使用值表达式进行比较。spring-doc.cadn.net.cn

声明的查询

尽管从方法名称派生的查询非常方便,但可能会面临这样的情况:方法名称解析器不支持要使用的关键字,或者方法名称会变得不必要地丑陋。因此,您可以通过命名约定使用 JPA 命名查询(有关更多信息,请参阅使用 JPA 命名查询),或者更确切地说,使用@Query(参见@Query了解详情)。spring-doc.cadn.net.cn

查询创建

通常,JPA 的查询创建机制的工作原理是 查询方法 中所述的。以下示例显示了 JPA 查询方法转换为:spring-doc.cadn.net.cn

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

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

我们使用 JPQL 创建一个查询,转换为以下查询:select u from User u where u.emailAddress = ?1 and u.lastname = ?2.Spring Data JPA 执行属性检查并遍历嵌套属性,如属性表达式中所述。spring-doc.cadn.net.cn

下表描述了 JPA 支持的关键字以及包含该关键字的方法转换为什么:spring-doc.cadn.net.cn

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

Distinctspring-doc.cadn.net.cn

findDistinctByLastnameAndFirstnamespring-doc.cadn.net.cn

select distinct …​ where x.lastname = ?1 and x.firstname = ?2spring-doc.cadn.net.cn

Andspring-doc.cadn.net.cn

findByLastnameAndFirstnamespring-doc.cadn.net.cn

… where x.lastname = ?1 and x.firstname = ?2spring-doc.cadn.net.cn

Orspring-doc.cadn.net.cn

findByLastnameOrFirstnamespring-doc.cadn.net.cn

… where x.lastname = ?1 or x.firstname = ?2spring-doc.cadn.net.cn

Is,Equalsspring-doc.cadn.net.cn

findByFirstname,findByFirstnameIs,findByFirstnameEqualsspring-doc.cadn.net.cn

… where x.firstname = ?1(或… where x.firstname IS NULL如果参数是null)spring-doc.cadn.net.cn

Betweenspring-doc.cadn.net.cn

findByStartDateBetweenspring-doc.cadn.net.cn

… where x.startDate between ?1 and ?2spring-doc.cadn.net.cn

LessThanspring-doc.cadn.net.cn

findByAgeLessThanspring-doc.cadn.net.cn

… where x.age < ?1spring-doc.cadn.net.cn

LessThanEqualspring-doc.cadn.net.cn

findByAgeLessThanEqualspring-doc.cadn.net.cn

… where x.age <= ?1spring-doc.cadn.net.cn

GreaterThanspring-doc.cadn.net.cn

findByAgeGreaterThanspring-doc.cadn.net.cn

… where x.age > ?1spring-doc.cadn.net.cn

GreaterThanEqualspring-doc.cadn.net.cn

findByAgeGreaterThanEqualspring-doc.cadn.net.cn

… where x.age >= ?1spring-doc.cadn.net.cn

Afterspring-doc.cadn.net.cn

findByStartDateAfterspring-doc.cadn.net.cn

… where x.startDate > ?1spring-doc.cadn.net.cn

Beforespring-doc.cadn.net.cn

findByStartDateBeforespring-doc.cadn.net.cn

… where x.startDate < ?1spring-doc.cadn.net.cn

IsNull,Nullspring-doc.cadn.net.cn

findByAge(Is)Nullspring-doc.cadn.net.cn

… where x.age is nullspring-doc.cadn.net.cn

IsNotNull,NotNullspring-doc.cadn.net.cn

findByAge(Is)NotNullspring-doc.cadn.net.cn

… where x.age is not nullspring-doc.cadn.net.cn

Likespring-doc.cadn.net.cn

findByFirstnameLikespring-doc.cadn.net.cn

… where x.firstname like ?1spring-doc.cadn.net.cn

NotLikespring-doc.cadn.net.cn

findByFirstnameNotLikespring-doc.cadn.net.cn

… where x.firstname not like ?1spring-doc.cadn.net.cn

StartingWithspring-doc.cadn.net.cn

findByFirstnameStartingWithspring-doc.cadn.net.cn

… where x.firstname like ?1(参数绑定,附加%)spring-doc.cadn.net.cn

EndingWithspring-doc.cadn.net.cn

findByFirstnameEndingWithspring-doc.cadn.net.cn

… where x.firstname like ?1(参数绑定为前置%)spring-doc.cadn.net.cn

Containingspring-doc.cadn.net.cn

findByFirstnameContainingspring-doc.cadn.net.cn

… where x.firstname like ?1(参数绑定包装在%)spring-doc.cadn.net.cn

OrderByspring-doc.cadn.net.cn

findByAgeOrderByLastnameDescspring-doc.cadn.net.cn

… where x.age = ?1 order by x.lastname descspring-doc.cadn.net.cn

Notspring-doc.cadn.net.cn

findByLastnameNotspring-doc.cadn.net.cn

… where x.lastname <> ?1(或… where x.lastname IS NOT NULL如果参数是null)spring-doc.cadn.net.cn

Inspring-doc.cadn.net.cn

findByAgeIn(Collection<Age> ages)spring-doc.cadn.net.cn

… where x.age in ?1spring-doc.cadn.net.cn

NotInspring-doc.cadn.net.cn

findByAgeNotIn(Collection<Age> ages)spring-doc.cadn.net.cn

… where x.age not in ?1spring-doc.cadn.net.cn

Truespring-doc.cadn.net.cn

findByActiveTrue()spring-doc.cadn.net.cn

… where x.active = truespring-doc.cadn.net.cn

Falsespring-doc.cadn.net.cn

findByActiveFalse()spring-doc.cadn.net.cn

… where x.active = falsespring-doc.cadn.net.cn

IgnoreCasespring-doc.cadn.net.cn

findByFirstnameIgnoreCasespring-doc.cadn.net.cn

… where UPPER(x.firstname) = UPPER(?1)spring-doc.cadn.net.cn

InNotIn还采用Collection作为参数以及数组或变量。对于同一逻辑运算符的其他语法版本,请检查存储库查询关键字

DISTINCT可能很棘手,并不总是产生您期望的结果。 例如select distinct u from User u将产生与select distinct u.lastname from User u. 在第一种情况下,由于您包含User.id,不会重复任何内容,因此您将获得整个表,并且它将是User对象。spring-doc.cadn.net.cn

然而,后一个查询会将焦点缩小到仅User.lastname并查找该表的所有唯一姓氏。这也会产生一个List<String>结果集而不是List<User>结果集。spring-doc.cadn.net.cn

countDistinctByLastname(String lastname)也可能产生意想不到的结果。Spring Data JPA 将派生select count(distinct u.id) from User u where u.lastname = ?1. 同样,自u.id不会命中任何重复项,则此查询将计算具有绑定姓氏的所有用户。这将与countByLastname(String lastname)!spring-doc.cadn.net.cn

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

基于注释的配置

基于注释的配置的优点是不需要编辑另一个配置文件,从而减少了维护工作。您需要为每个新的查询声明重新编译域类来支付这种好处。spring-doc.cadn.net.cn

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

}

使用 JPA 命名查询

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

XML 命名查询定义

要使用 XML 配置,请添加必要的<named-query />元素添加到orm.xmlJPA 配置文件位于META-INF文件夹。使用一些定义的命名约定启用了命名查询的自动调用。有关更多详细信息,请参阅下文。spring-doc.cadn.net.cn

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

查询具有一个特殊名称,用于在运行时解析它。spring-doc.cadn.net.cn

声明接口

要允许这些命名查询,请指定UserRepository如下:spring-doc.cadn.net.cn

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

  List<User> findByLastname(String lastname);

  User findByEmailAddress(String emailAddress);
}

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

@Query

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

注释到查询方法的查询优先于使用@NamedQueryorm.xml.spring-doc.cadn.net.cn

以下示例显示了使用@Query注解:spring-doc.cadn.net.cn

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

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

使用高级LIKE表达 式

手动定义的查询的查询运行机制@Query允许定义高级LIKE表达式,如以下示例所示:spring-doc.cadn.net.cn

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

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

在前面的示例中,LIKE分隔符字符 () 被识别,并且查询被转换为有效的 JPQL 查询(删除 )。运行查询时,传递给方法调用的参数将使用先前识别的%%LIKE模式。spring-doc.cadn.net.cn

本机查询

使用@NativeQuery注释允许运行本机查询,如以下示例所示:spring-doc.cadn.net.cn

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

  @NativeQuery(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1")
  User findByEmailAddress(String emailAddress);
}
@NativeQuery注释主要是@Query(nativeQuery=true)但它也提供了额外的属性,例如sqlResultSetMapping利用 JPA 的@SqlResultSetMapping(…).
Spring Data 可以重写简单的分页和排序查询。 更复杂的查询需要 JSqlParser 位于类路径上或countQuery在代码中声明。 有关更多详细信息,请参阅下面的示例。
示例 8.使用@NativeQuery
public interface UserRepository extends JpaRepository<User, Long> {

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

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

除了获取映射结果之外,本机查询还允许您读取原始结果Tuple通过选择Mapcontainer 作为方法的返回类型。 生成的映射包含表示实际数据库列名和值的键/值对。spring-doc.cadn.net.cn

示例 9.返回原始列名/值对的本机查询
interface UserRepository extends JpaRepository<User, Long> {

  @NativeQuery("SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1")
  Map<String, Object> findRawMapByEmail(String emailAddress);      (1)

  @NativeQuery("SELECT * FROM USERS WHERE LASTNAME = ?1")
  List<Map<String, Object>> findRawMapByLastname(String lastname); (2)
}
1 Map结果由Tuple.
2 倍数Map结果支持Tuples.
基于字符串的元组查询仅受 Hibernate 支持。 Eclipselink 仅支持基于条件的元组查询。

查询自省和重写

Spring Data JPA 提供了广泛的功能,可用于运行各种类型的查询。 具体来说,给定一个声明的查询,Spring Data JPA 可以:spring-doc.cadn.net.cn

为此,我们附带了特定于 HQL(休眠)和 EQL(EclipseLink)方言的查询解析器,因为这些方言定义明确。 另一方面,SQL 允许方言之间存在相当大的差异。 因此,Spring Data 无法支持所有级别的查询复杂性。 我们不是通用的 SQL 解析器库,而是通过简化查询执行来提高开发人员生产力的库。 我们内置的 SQL 查询增强器仅支持用于自省的简单查询COUNT查询派生。 更复杂的查询将需要使用 JSqlParser 或您提供COUNT查询@Query(countQuery=…). 如果 JSqlParser 在类路径上,Spring Data JPA 将使用它进行本机查询。spring-doc.cadn.net.cn

要对选择进行细粒度控制,您可以配置QueryEnhancerSelector@EnableJpaRepositories:spring-doc.cadn.net.cn

示例 10.使用 JavaConfig 的 Spring Data JPA 存储库
@Configuration
@EnableJpaRepositories(queryEnhancerSelector = MyQueryEnhancerSelector.class)
class ApplicationConfig {
  // …
}

QueryEnhancerSelector是一个策略接口,旨在选择QueryEnhancer基于特定查询。 您也可以提供自己的QueryEnhancer如果需要,可以实现。spring-doc.cadn.net.cn

应用 QueryRewriter

有时,无论您尝试应用多少功能,似乎都不可能让 Spring Data JPA 在将查询发送到EntityManager.spring-doc.cadn.net.cn

您可以在查询发送到EntityManager并“重写”它。 也就是说,您可以在最后一刻进行任何更改。 查询重写适用于实际查询,并在适用时应用于计数查询。 计数查询经过优化,因此,要么不需要,要么通过其他方式获得计数,例如从休眠SelectionQuery如果有封闭交易。spring-doc.cadn.net.cn

示例 11.使用@Query
public interface MyRepository extends JpaRepository<User, Long> {

		@NativeQuery(value = "select original_user_alias.* from SD_USER original_user_alias",
				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 查询,两者都利用相同的QueryRewriter. 在这种情况下,Spring Data JPA 将查找在相应类型的应用程序上下文中注册的 bean。spring-doc.cadn.net.cn

您可以像这样编写查询重写器:spring-doc.cadn.net.cn

示例 12.例QueryRewriter
public class MyQueryRewriter implements QueryRewriter {

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

您必须确保您的QueryRewriter在应用程序上下文中注册,无论是通过应用 Spring Framework 的@Component-based 注释,或将其作为@Bean方法@Configuration类。spring-doc.cadn.net.cn

另一种选择是让存储库本身实现接口。spring-doc.cadn.net.cn

示例 13.提供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");
		}
}

取决于您使用QueryRewriter,建议使用多个,每个都注册到应用程序上下文中。spring-doc.cadn.net.cn

在基于 CDI 的环境中,Spring Data JPA 将搜索BeanManager对于实现QueryRewriter.

使用排序

排序可以通过提供PageRequest或通过使用Sort径直。在Order实例Sort需要匹配您的域模型,这意味着它们需要解析为查询中使用的属性或别名。JPQL 将其定义为状态字段路径表达式。spring-doc.cadn.net.cn

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

但是,使用Sort@Query让你潜入非路径检查Order实例包含ORDER BY第。这是可能的,因为Order附加到给定的查询字符串中。默认情况下,Spring Data JPA 拒绝任何Order实例包含函数调用,但您可以使用JpaSort.unsafe添加潜在的不安全排序。spring-doc.cadn.net.cn

以下示例使用SortJpaSort,包括JpaSort:spring-doc.cadn.net.cn

示例 14.用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 有效Sort包含明确不安全的 Order.
4 有效Sort指向别名函数的表达式。

JpaSort.unsafe(...) 限制

JpaSort.unsafe(…)以两种模式运行:spring-doc.cadn.net.cn

  • 当与派生查询或基于字符串的查询一起使用时,顺序字符串将附加到查询中。spring-doc.cadn.net.cn

  • 当与按示例查询或规范一起使用时(使用CriteriaQuery),则 order 表达式被解析并添加到CriteriaQuery作为表达式。查询表达式可以包含函数调用、各种子句(例如CASE WHEN、算术表达式)或属性路径。顺序翻译不支持子查询表达式,TREATCAST.`spring-doc.cadn.net.cn

滚动大型查询结果

处理大型数据集时,滚动有助于有效地处理这些结果,而无需将所有结果加载到内存中。spring-doc.cadn.net.cn

有多个选项来使用大型查询结果:spring-doc.cadn.net.cn

  1. 分页。 您在上一章中了解了PageablePageRequest.spring-doc.cadn.net.cn

  2. 基于偏移量的滚动。 这是比分页更轻的变体,因为它不需要总结果计数。spring-doc.cadn.net.cn

  3. 基于键集的滚动。 这种方法通过利用数据库索引避免了基于偏移量的结果检索的缺点spring-doc.cadn.net.cn

阅读更多关于最适合您的特定安排的方法的信息。spring-doc.cadn.net.cn

可以将 Scroll API 与查询方法、Query-by-ExampleQuerydsl 一起使用。spring-doc.cadn.net.cn

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

使用命名参数

默认情况下,Spring Data JPA 使用基于位置的参数绑定,如前面所有示例中所述。 这使得查询方法在重构参数位置时有点容易出错。 要解决此问题,您可以使用@Param注释,为方法参数提供具体名称并在查询中绑定该名称,如以下示例所示:spring-doc.cadn.net.cn

示例 15.使用命名参数
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命名参数的注释。

模板化查询和表达式

我们支持在手动定义的查询中使用受限表达式,这些查询定义为@Query. 运行查询时,将根据一组预定义的变量评估这些表达式。spring-doc.cadn.net.cn

如果您不熟悉值表达式,请参阅值表达式基础知识以了解 SpEL 表达式和属性占位符。

Spring Data JPA 支持一个名为entityName. 它的用法是select x from #{#entityName} x. 它插入entityName与给定存储库关联的域类型。 这entityName解决如下: * 如果域类型在@Entity注释,则使用它。 * 否则,使用域类型的简单类名。spring-doc.cadn.net.cn

以下示例演示了#{#entityName}表达式,在其中要定义具有查询方法和手动定义查询的存储库接口:spring-doc.cadn.net.cn

示例 16.在存储库查询方法中使用 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-doc.cadn.net.cn

entityName可以使用@Entity注解。 中的自定义orm.xmlSpEL 表达式不支持。

当然,你可以直接使用User直接在查询声明中,但这也需要您更改查询。 对#entityName拾取未来可能的重新映射Userclass 设置为不同的实体名称(例如,通过使用@Entity(name = "MyUser").spring-doc.cadn.net.cn

另一个用例#{#entityName}表达式是如果要为具体域类型定义具有专用存储库接口的通用存储库接口。 若要不在具体接口上重复自定义查询方法的定义,可以在@Queryannotation,如以下示例所示:spring-doc.cadn.net.cn

示例 17.在存储库查询方法中使用 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> { … }

在前面的示例中,MappedTypeRepositoryinterface 是一些扩展的域类型的通用父接口AbstractMappedType. 它还定义了通用findAllByAttribute(…)方法,可用于专用存储库接口的实例。 如果您现在调用findAllByAttribute(…)ConcreteRepository,则查询将变为select t from ConcreteType t where t.attribute = ?1.spring-doc.cadn.net.cn

您还可以使用表达式来控制参数,也可以用于控制方法参数。 在这些表达式中,实体名称不可用,但参数可用。 可以通过名称或索引访问它们,如以下示例所示。spring-doc.cadn.net.cn

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

like-条件通常希望附加到 String 值参数的开头或结尾。 这可以通过在绑定参数标记或 SpEL 表达式前附加或前缀 来完成。 同样,以下示例演示了这一点。%%spring-doc.cadn.net.cn

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

使用时like-条件,其值来自不安全源时,应对值进行清理,以便它们不能包含任何通配符,从而允许攻击者选择比他们应该能够选择的更多的数据。 为此,该escape(String)方法在 SpEL 上下文中可用。 它为第一个参数中的 和 的所有实例加上第二个参数中的单个字符。 结合_%escape的子句like表达式在 JPQL 和标准 SQL 中可用,这允许轻松清理绑定参数。spring-doc.cadn.net.cn

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

给定存储库接口中的此方法声明findContainingEscaped("Peter_")会发现Peter_Parker但不是Peter Parker. 可以使用的转义字符通过设置escapeCharacter@EnableJpaRepositories注解。 请注意,该方法escape(String)available 将仅转义 SQL 和 JPQL 标准通配符和 . 如果底层数据库或 JPA 实现支持其他通配符,则这些通配符将不会被转义。_%spring-doc.cadn.net.cn

示例 21.在存储库查询方法中使用值表达式:配置属性
@Query("select u from User u where u.applicationName = ?${spring.application.name:unknown}")
List<User> findContainingEscaped(String namePart);

如果您希望从Environment在运行时。 在查询执行时对属性进行评估。 通常,属性占位符解析为类似字符串的值。spring-doc.cadn.net.cn

其他方法

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

当您需要最大限度地控制查询,同时仍然让 Spring Data JPA 提供资源管理时,这些策略可能是最有效的。spring-doc.cadn.net.cn

修改查询

前面的所有部分都介绍了如何声明查询以访问给定实体或实体集合。 您可以使用 Spring Data Repositories 的自定义实现中描述的自定义方法工具来添加自定义修改行为。 由于这种方法对于全面的自定义功能是可行的,因此您可以通过使用@Modifying,如以下示例所示:spring-doc.cadn.net.cn

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

这样做会触发注释到该方法的查询作为更新查询,而不是选择查询。作为EntityManager可能包含过时的实体,我们不会自动清除它(参见 JavaDocEntityManager.clear()有关详细信息),因为这实际上会删除所有仍在EntityManager. 如果您希望EntityManager要自动清除,您可以将@Modifying注释的clearAutomatically属性设置为true.spring-doc.cadn.net.cn

@Modifying注释仅与@Query注解。 派生查询方法或自定义方法不需要此注解。spring-doc.cadn.net.cn

派生删除查询

Spring Data JPA还支持派生删除查询,使您不必显式声明JPQL查询,如以下示例所示:spring-doc.cadn.net.cn

实施例 23.使用派生删除查询
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);
}

尽管deleteByRoleId(…)方法看起来它基本上产生与deleteInBulkByRoleId(…),这两个方法声明在运行方式方面存在重要差异。 顾名思义,后一种方法对数据库发出单个 JPQL 查询(注释中定义的查询)。 这意味着即使是当前加载的User看不到调用的生命周期回调。spring-doc.cadn.net.cn

为确保实际调用生命周期查询,请调用deleteByRoleId(…)运行查询,然后逐个删除返回的实例,以便持久性提供程序可以实际调用@PreRemove这些实体的回调。spring-doc.cadn.net.cn

事实上,派生的删除查询是运行查询然后调用CrudRepository.delete(Iterable<User> users)在结果上保持行为与其他delete(…)方法CrudRepository.spring-doc.cadn.net.cn

删除大量对象时,您需要考虑性能影响,以确保足够的内存可用性。所有生成的对象在被删除之前都会加载到内存中,并保留在会话中,直到刷新或完成事务。

应用查询提示

要将 JPA 查询提示应用于存储库接口中声明的查询,您可以使用@QueryHints注解。它采用 JPA 数组@QueryHint注释加上布尔标志,以可能禁用应用于应用分页时触发的其他计数查询的提示,如以下示例所示:spring-doc.cadn.net.cn

实施例 24.将 QueryHints 与存储库方法一起使用
public interface UserRepository extends Repository<User, Long> {

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

前面的声明将应用配置的@QueryHint对于该实际查询,但省略将其应用于触发的计数查询以计算总页数。spring-doc.cadn.net.cn

向查询添加注释

有时,您需要根据数据库性能调试查询。 数据库管理员向您显示的查询可能看起来与您使用的内容非常不同@Query,或者它可能看起来 与您假设的 Spring Data JPA 生成的有关自定义查找器或您使用示例查询的内容完全不同。spring-doc.cadn.net.cn

为了使此过程更容易,您可以在几乎任何 JPA作中插入自定义注释,无论是查询还是其他作 通过应用@Meta注解。spring-doc.cadn.net.cn

实施例 25.应用@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. 无论哪种方式,@Meta注释允许您添加comment在将查询发送到数据库之前,它们将入到查询中。spring-doc.cadn.net.cn

同样重要的是要注意,此功能不仅限于查询。它扩展到countexists操作。 虽然没有显示,但它也扩展到某些delete操作。spring-doc.cadn.net.cn

虽然我们尝试在所有可能的地方应用此功能,但底层EntityManager不支持评论。例如entityManager.createQuery()被明确记录为支持评论,但是entityManager.find()作则不然。

JPQL 日志记录和 SQL 日志记录都不是 JPA 中的标准,因此每个提供程序都需要自定义配置,如下图所示。spring-doc.cadn.net.cn

激活休眠注释

要在 Hibernate 中激活查询注释,您必须将hibernate.use_sql_commentstrue.spring-doc.cadn.net.cn

如果您使用的是基于 Java 的配置设置,则可以这样完成:spring-doc.cadn.net.cn

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

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

如果你有一个persistence.xml文件,您可以在那里应用它:spring-doc.cadn.net.cn

实施例 27.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.properties文件:spring-doc.cadn.net.cn

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

要在 EclipseLink 中激活查询注释,您必须将eclipselink.logging.level.sqlFINE.spring-doc.cadn.net.cn

如果您使用的是基于 Java 的配置设置,则可以这样完成:spring-doc.cadn.net.cn

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

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

如果你有一个persistence.xml文件,您可以在那里应用它:spring-doc.cadn.net.cn

实施例 30.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.properties文件:spring-doc.cadn.net.cn

实施例 31.Spring Boot 基于属性的配置
spring.jpa.properties.eclipselink.logging.level.sql=FINE

配置 Fetch- 和 LoadGraphs

JPA 2.1 规范引入了对指定 Fetch- 和 LoadGraphs 的支持,我们还支持@EntityGraph注释,它允许您引用@NamedEntityGraph定义。您可以在实体上使用该注释来配置生成查询的提取计划。类型 (FetchLoad) 可以通过使用type属性@EntityGraph注解。有关进一步的参考,请参阅 JPA 2.1 规范 3.7.4。spring-doc.cadn.net.cn

以下示例演示如何在实体上定义命名实体图:spring-doc.cadn.net.cn

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

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

  …
}

以下示例演示如何在存储库查询方法上引用命名实体图:spring-doc.cadn.net.cn

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

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

}

还可以使用@EntityGraph.提供的attributePaths被翻译成EntityGraph无需显式添加@NamedEntityGraph添加到您的域类型,如以下示例所示:spring-doc.cadn.net.cn

实施例 34.在存储库查询方法上使用临时实体图定义
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

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

}

滚动

滚动是一种更细粒度的方法,用于迭代更大的结果集块。 滚动由稳定排序、滚动类型(基于偏移量或键集的滚动)和结果限制组成。 您可以使用属性名称定义简单的排序表达式,并使用TopFirst关键词通过查询派生。 您可以连接表达式以将多个条件收集到一个表达式中。spring-doc.cadn.net.cn

滚动查询返回Window<T>允许获取元素的滚动位置以获取下一个Window<T>直到您的应用程序使用了整个查询结果。 类似于使用 JavaIterator<List<…>>通过获取下一批结果,查询结果滚动允许您访问ScrollPosition通过Window.positionAt(…​).spring-doc.cadn.net.cn

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());

ScrollPosition标识具有整个查询结果的元素的确切位置。 查询执行将位置参数视为排他性,结果将在给定位置之后开始。ScrollPosition#offset()ScrollPosition#keyset()作为ScrollPosition指示滚动作的开始。spring-doc.cadn.net.cn

上面的示例显示了静态排序和限制。 您也可以定义接受Sort对象定义更复杂的排序顺序或基于每个请求的排序。 以类似的方式,提供Limit对象允许您根据每个请求定义动态限制,而不是应用静态限制。 在查询方法详细信息中阅读有关动态排序和限制的更多信息。spring-doc.cadn.net.cn

WindowIterator提供了一个实用程序来简化滚动Windows 通过消除检查是否存在下一个Window并应用ScrollPosition.spring-doc.cadn.net.cn

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

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

使用偏移滚动

偏移滚动使用类似于分页的 Offset 计数器来跳过许多结果,并让数据源仅返回从给定 Offset 开始的结果。 这种简单的机制避免了将大量结果发送到客户端应用程序。 但是,大多数数据库需要在服务器返回结果之前具体化完整的查询结果。spring-doc.cadn.net.cn

示例 35.用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.

之间有区别ScrollPosition.offset()ScrollPosition.offset(0L). 前者表示滚动作的开始,指向没有特定的偏移量,而后者标识第一个元素(在位置0) 的结果。 鉴于滚动的排他性,使用ScrollPosition.offset(0)跳过第一个元素并转换为1.spring-doc.cadn.net.cn

使用键集过滤滚动

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

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

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

键集过滤要求键集属性(用于排序的属性)不可为空。 此限制适用于特定于商店null比较运算符的值处理以及对索引源运行查询的需要。 对可为 null 属性的键集过滤将导致意外结果。spring-doc.cadn.net.cn

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-doc.cadn.net.cn

可以使用接口和 DTO 投影,但请确保包含排序依据的所有属性,以避免键集提取失败。spring-doc.cadn.net.cn

指定您的Sortorder,则包含与查询相关的排序属性就足够了; 如果您不想,则无需确保唯一的查询结果。 键集查询机制通过包含主键(或复合主键的任何其余部分)来修改排序顺序,以确保每个查询结果都是唯一的。spring-doc.cadn.net.cn