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作为通配符,这些将被转义,因此它们仅作为 Literals 匹配。 使用的转义字符可以通过设置escapeCharacter@EnableJpaRepositories注解。 与 Using Value Expressions 比较。spring-doc.cadn.net.cn

声明的查询

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

查询创建

通常,JPA 的查询创建机制的工作方式如 Query Methods 中所述。以下示例显示了 JPA 查询方法的转换内容:spring-doc.cadn.net.cn

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

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

我们使用 JPA criteria API 从中创建一个查询,但从本质上讲,这将转换为以下查询: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 <> ?1spring-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作为参数以及数组或 varargs 进行。对于同一逻辑运算符的其他语法版本,请检查 Repository query keywords

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不会命中任何重复项,则此查询将计算具有 Binding Last Name 的所有用户。 这与countByLastname(String lastname)!spring-doc.cadn.net.cn

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

基于注释的配置

基于 Comments 的配置的优点是不需要编辑另一个配置文件,从而减少了维护工作。您需要为每个新的查询声明重新编译域类,从而为该优势付费。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注解,而不是将它们注解到 Domain 类。这样可以将域类从持久性特定信息中解放出来,并将查询归置到存储库界面。spring-doc.cadn.net.cn

注释到 query 方法的查询优先于使用@NamedQuery或在orm.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);
}

应用 QueryRewriter

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

您可以在查询发送到EntityManager并 “重写” 它。那是 您可以在最后一刻进行任何更改。spring-doc.cadn.net.cn

例 6.使用@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

例 7.例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的注解,或者将其作为@Bean方法中@Configuration类。spring-doc.cadn.net.cn

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

例 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");
		}
}

根据您对QueryRewriter,则建议有多个 Cookie,每个 Cookie 都向 应用程序上下文。spring-doc.cadn.net.cn

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

使用高级LIKE表达 式

使用@Query允许定义高级LIKE表达式,如以下示例所示:spring-doc.cadn.net.cn

例 9.高深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);
}

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

本机查询

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

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

  @NativeQuery(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1")
  User findByEmailAddress(String emailAddress);
}
@NativeQueryannotation 主要是@Query(nativeQuery=true)但它也提供了其他属性,例如sqlResultSetMapping利用 JPA 的@SqlResultSetMapping(…).
Spring Data 可以重写用于分页和排序的简单查询。 更复杂的查询要求 JSqlParser 位于类路径上,或者要求countQuery在您的代码中声明。 有关更多详细信息,请参阅下面的示例。
例 11.使用 query 方法声明用于分页的本机计数查询@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);
}

可以禁用JSqlParser用于解析本机查询,尽管它在 Classpath 上可以通过设置spring.data.jpa.query.native.parser=regex通过spring.properties文件或系统属性。spring-doc.cadn.net.cn

有效值为 (不区分大小写):spring-doc.cadn.net.cn

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

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

例 12.本机查询重新调整原始列名称/值对
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 Mapresult 由Tuple.
2 倍数Map结果支持Tuples.
基于字符串的元组查询仅受 Hibernate 支持。 Eclipselink 仅支持基于标准的元组查询。

使用 Sort

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

使用任何不可引用的 path 表达式都会导致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

例 13.用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 有效Sortexpression 指向别名函数。

滚动大型查询结果

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

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

  1. 分页。 您在上一章中已经学习了PageablePageRequest.spring-doc.cadn.net.cn

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

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

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

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

尚不支持使用基于 String 的查询方法进行滚动。 也不支持使用 stored 进行滚动@Procedurequery 方法。

使用命名参数

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

例 14.使用命名参数
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 的参数名称发现,该参数基于-parameterscompiler 标志。通过在构建中使用此标志作为调试信息的替代方法,您可以省略@Param注解。

使用表达式

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

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

Spring Data JPA 支持一个名为entityName. 它的用法是select x from #{#entityName} x. 它会将entityName的域类型。 这entityName解析如下: * 如果域类型在@Entityannotation 时,会使用它。 * 否则,将使用域类型的简单 class-name。spring-doc.cadn.net.cn

以下示例演示了#{#entityName}expression 在查询字符串中,您希望使用查询方法和手动定义的查询定义存储库接口:spring-doc.cadn.net.cn

例 15.在存储库查询方法中使用 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.xml不支持 SPEL 表达式。

当然,您可以只使用User,但这还需要您更改查询。 对#entityName选取User类设置为其他实体名称(例如,通过使用@Entity(name = "MyUser").spring-doc.cadn.net.cn

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

例 16.在存储库查询方法中使用 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(…)方法,该方法可以在专用存储库接口的实例上使用。 如果您现在调用findByAllAttribute(…)ConcreteRepository,则查询将变为select t from ConcreteType t where t.attribute = ?1.spring-doc.cadn.net.cn

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

例 17.在存储库查询方法中使用值表达式:访问参数
@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-conditions 通常希望附加到 String 值参数的开头或结尾。 这可以通过在绑定参数标记或 SPEL 表达式前附加或作为其前缀来完成。 以下示例再次演示了这一点。%%spring-doc.cadn.net.cn

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

使用like-conditions 的值来自不安全的来源,则应清理这些值,以便它们不能包含任何通配符,从而允许攻击者选择比他们应该能够选择的数据更多的数据。 为此,escape(String)方法在 SPEL 上下文中可用。 它用第二个参数中的单个字符作为第一个参数中 and 的所有实例的前缀。 与_%escape子句的like表达式,这允许轻松清理绑定参数。spring-doc.cadn.net.cn

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

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

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

如果您希望从Environment在运行时。 在执行查询时评估该属性。 通常,属性占位符解析为类似 String 的值。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

例 21.声明作查询
@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

例 22.使用派生的删除查询
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 查询(在 annotation 中定义的那个)。 这意味着,即使是当前加载的User看不到调用的生命周期回调。spring-doc.cadn.net.cn

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

实际上,派生的 delete 查询是运行查询,然后调用CrudRepository.delete(Iterable<User> users)并保持行为与其他delete(…)methods 中的CrudRepository.spring-doc.cadn.net.cn

应用查询提示

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

例 23.将 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,但省略将其应用于触发的 count 查询,以计算总页数。spring-doc.cadn.net.cn

向查询添加注释

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

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

例 24.应用@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. 无论哪种方式,@Metaannotation 允许您添加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 中激活查询注释,您必须设置hibernate.use_sql_commentstrue.spring-doc.cadn.net.cn

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

例 25.基于 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

例 26.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

例 27.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

例 28.基于 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

例 29.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

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

配置 Fetch- 和 LoadGraph

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

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

例 31.在实体上定义命名实体图。
@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

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

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

}

还可以使用 Ad Hoc 实体图@EntityGraph.提供的attributePaths被转换为相应的EntityGraph而无需显式添加@NamedEntityGraph添加到您的域类型,如以下示例所示:spring-doc.cadn.net.cn

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

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

}

滚动

滚动是一种更精细的方法,用于迭代较大的结果集块。 滚动包括稳定排序、滚动类型(基于 Offset 或 Keyset 的滚动)和结果限制。 您可以使用属性名称定义简单的排序表达式,并使用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标识元素与整个查询结果的确切位置。 查询执行将 position 参数视为 exclusive ,结果将在给定位置之后开始。ScrollPosition#offset()ScrollPosition#keyset()作为ScrollPosition指示滚动作的开始。spring-doc.cadn.net.cn

上面的示例显示了静态排序和限制。 您可以选择定义接受Sortobject 定义更复杂的排序顺序或基于每个请求的排序。 以类似的方式,提供LimitObject 允许您基于每个请求定义动态限制,而不是应用静态限制。 在 Query Methods Details 中阅读有关动态排序和限制的更多信息。spring-doc.cadn.net.cn

WindowIterator提供用于简化滚动的实用程序Windows,无需检查是否存在 nextWindow并应用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 计数器来跳过许多结果,并让数据源仅返回从给定 Offset 开始的结果。 这种简单的机制可避免将大型结果发送到客户端应用程序。 但是,大多数数据库都需要在服务器返回结果之前具体化完整的查询结果。spring-doc.cadn.net.cn

例 34.用OffsetScrollPosition使用 Repository Query Methods
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.

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

使用 Keyset-Filtering 进行滚动

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

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

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

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

KeysetScrollPosition使用 Repository Query Methods
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 从头开始,不要应用其他筛选。

当您的数据库包含与排序字段匹配的索引时,Keyset-Filtering 效果最佳,因此静态排序效果很好。 应用 Keyset-Filtering 的滚动查询需要查询返回的排序顺序中使用的属性,并且这些属性必须映射到返回的实体中。spring-doc.cadn.net.cn

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

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