此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data JPA 3.4.0spring-doc.cn

JPA 查询方法

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

查询查找策略

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

带有谓词 、 、 、 、 的派生查询,这些查询的相应参数将被清理。 这意味着,如果参数实际上包含由通配符识别的字符,这些字符将被转义,因此它们仅作为文本匹配。 使用的转义字符可以通过设置 annotation 来配置。 与 Using Value Expressions 比较。IsStartingWithStartingWithStartsWithIsEndingWithEndingWithEndsWithIsNotContainingNotContainingNotContainsIsContainingContainingContainsLIKEescapeCharacter@EnableJpaRepositoriesspring-doc.cn

声明的查询

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

查询创建

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

示例 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-doc.cn

下表描述了 JPA 支持的关键字以及包含该关键字的方法的转换内容:spring-doc.cn

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

Distinctspring-doc.cn

findDistinctByLastnameAndFirstnamespring-doc.cn

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

Andspring-doc.cn

findByLastnameAndFirstnamespring-doc.cn

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

Orspring-doc.cn

findByLastnameOrFirstnamespring-doc.cn

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

Is,Equalsspring-doc.cn

findByFirstname,,findByFirstnameIsfindByFirstnameEqualsspring-doc.cn

… where x.firstname = ?1(或者如果参数是… where x.firstname IS NULLnull)spring-doc.cn

Betweenspring-doc.cn

findByStartDateBetweenspring-doc.cn

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

LessThanspring-doc.cn

findByAgeLessThanspring-doc.cn

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

LessThanEqualspring-doc.cn

findByAgeLessThanEqualspring-doc.cn

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

GreaterThanspring-doc.cn

findByAgeGreaterThanspring-doc.cn

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

GreaterThanEqualspring-doc.cn

findByAgeGreaterThanEqualspring-doc.cn

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

Afterspring-doc.cn

findByStartDateAfterspring-doc.cn

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

Beforespring-doc.cn

findByStartDateBeforespring-doc.cn

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

IsNull,Nullspring-doc.cn

findByAge(Is)Nullspring-doc.cn

… where x.age is nullspring-doc.cn

IsNotNull,NotNullspring-doc.cn

findByAge(Is)NotNullspring-doc.cn

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

Likespring-doc.cn

findByFirstnameLikespring-doc.cn

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

NotLikespring-doc.cn

findByFirstnameNotLikespring-doc.cn

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

StartingWithspring-doc.cn

findByFirstnameStartingWithspring-doc.cn

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

EndingWithspring-doc.cn

findByFirstnameEndingWithspring-doc.cn

… where x.firstname like ?1(参数绑定并带有前置%)spring-doc.cn

Containingspring-doc.cn

findByFirstnameContainingspring-doc.cn

… where x.firstname like ?1(参数绑定%)spring-doc.cn

OrderByspring-doc.cn

findByAgeOrderByLastnameDescspring-doc.cn

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

Notspring-doc.cn

findByLastnameNotspring-doc.cn

… where x.lastname <> ?1spring-doc.cn

Inspring-doc.cn

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

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

NotInspring-doc.cn

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

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

Truespring-doc.cn

findByActiveTrue()spring-doc.cn

… where x.active = truespring-doc.cn

Falsespring-doc.cn

findByActiveFalse()spring-doc.cn

… where x.active = falsespring-doc.cn

IgnoreCasespring-doc.cn

findByFirstnameIgnoreCasespring-doc.cn

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

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

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

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

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

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

基于注释的配置

基于 Comments 的配置的优点是不需要编辑另一个配置文件,从而减少了维护工作。您需要为每个新的查询声明重新编译域类,从而为该优势付费。spring-doc.cn

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

}

使用 JPA 命名查询

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

XML 命名查询定义

要使用 XML 配置,请将必要的元素添加到位于 Classpath 文件夹中的 JPA 配置文件中。通过使用某些定义的命名约定来启用命名查询的自动调用。有关更多详细信息,请参阅下文。<named-query />orm.xmlMETA-INFspring-doc.cn

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

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

声明接口

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

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

  List<User> findByLastname(String lastname);

  User findByEmailAddress(String emailAddress);
}

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

@Query

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

注释到 query 方法的查询优先于 使用 中定义的查询或 在 中声明的命名查询。@NamedQueryorm.xmlspring-doc.cn

以下示例显示了使用注释创建的查询:@Queryspring-doc.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 应用所有内容 您希望在将查询发送到 .EntityManagerspring-doc.cn

您可以在查询被发送到 之前获得查询并 “重写” 它。那是 您可以在最后一刻进行任何更改。EntityManagerspring-doc.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 查询,两者都利用相同的 . 在这种情况下, Spring Data JPA 将查找在应用程序上下文中注册的相应类型的 bean。QueryRewriterspring-doc.cn

您可以编写如下所示的查询重写器:spring-doc.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");
     }
}

你必须确保在应用程序上下文中注册了你的,无论是通过应用 Spring Framework 的基于 Comments 之一,还是将其作为类中方法的一部分。QueryRewriter@Component@Bean@Configurationspring-doc.cn

另一种选择是让存储库本身实现接口。spring-doc.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");
		}
}

根据您对 执行的操作,建议有多个 ,每个 都向 应用程序上下文。QueryRewriterspring-doc.cn

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

使用高级表达式LIKE

用于创建的手动定义查询的查询运行机制允许在查询定义中定义高级表达式,如以下示例所示:@QueryLIKEspring-doc.cn

例 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-doc.cn

本机查询

使用注释可以运行本机查询,如以下示例所示:@NativeQueryspring-doc.cn

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

  @NativeQuery(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1")
  User findByEmailAddress(String emailAddress);
}
该注释主要是 for 的组合注释,但它也提供了其他属性,例如利用 JPA 的 .@NativeQuery@Query(nativeQuery=true)sqlResultSetMapping@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);
}

可以禁用 for 解析本机查询,尽管它可以通过 file 或 system 属性在 Classpath 上使用。JSqlParserspring.data.jpa.query.native.parser=regexspring.propertiesspring-doc.cn

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

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

除了获取映射结果之外,本机查询还允许您通过选择容器作为方法的返回类型来从数据库中读取原始数据。 生成的 map 包含表示实际数据库列名称和值的键/值对。TupleMapspring-doc.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 由 .MapTuple
2 由 s 支持的多个结果。MapTuple
基于字符串的元组查询仅受 Hibernate 支持。 Eclipselink 仅支持基于标准的元组查询。

使用 Sort

可以通过提供 a 或 using directly 进行排序。实例中实际使用的属性需要与您的域模型匹配,这意味着它们需要解析为查询中使用的属性或别名。JPQL 将其定义为状态字段路径表达式。PageRequestSortOrderSortspring-doc.cn

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

但是,与 @Query 一起使用可以让您偷偷进入包含子句中函数的非路径检查实例。这是可能的,因为 the 已附加到给定的查询字符串。默认情况下, Spring Data JPA 拒绝任何包含函数调用的实例,但您可以使用它来添加可能不安全的 Sequences。SortOrderORDER BYOrderOrderJpaSort.unsafespring-doc.cn

以下示例使用 和 ,包括 on 上的 unsafe 选项:SortJpaSortJpaSortspring-doc.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 Valid 包含显式 unsafeSortOrder
4 指向别名函数的有效表达式。Sort

滚动大型查询结果

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

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

  1. 分页。 您在上一章中已经了解了 和 。PageablePageRequestspring-doc.cn

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

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

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

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

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

使用命名参数

默认情况下,Spring Data JPA 使用基于位置的参数绑定,如前面的所有示例所述。 这使得查询方法在重构参数位置时有点容易出错。 要解决此问题,您可以使用 annotation 为方法参数指定具体名称,并在查询中绑定该名称,如以下示例所示:@Paramspring-doc.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 参数名称发现。通过在构建中使用此标志作为调试信息的替代方法,您可以省略命名参数的注释。-parameters@Param

使用表达式

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

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

Spring Data JPA 支持一个名为 . 它的用法是 。 它会插入与给定存储库关联的域类型的 。 解析如下: * 如果域类型在注释上设置了 name 属性,则使用它。 * 否则,将使用域类型的简单 class-name。entityNameselect x from #{#entityName} xentityNameentityName@Entityspring-doc.cn

以下示例演示了查询字符串中表达式的一个使用案例,您希望使用查询方法和手动定义的查询定义存储库接口:#{#entityName}spring-doc.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.cn

可以使用 Comments 进行自定义。 SPEL 表达式不支持自定义。entityName@Entityorm.xml

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

查询字符串中表达式的另一个用例是,如果要为具体域类型定义具有专用存储库接口的通用存储库接口。 要不在具体接口上重复自定义查询方法的定义,您可以在通用存储库接口中注释的查询字符串中使用实体名称表达式,如以下示例所示:#{#entityName}@Queryspring-doc.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> { … }

在前面的示例中,该接口是一些扩展 的域类型的公共父接口。 它还定义了 generic 方法,该方法可用于专用存储库接口的实例。 如果现在调用 on ,则查询将变为 。MappedTypeRepositoryAbstractMappedTypefindAllByAttribute(…)findByAllAttribute(…)ConcreteRepositoryselect t from ConcreteType t where t.attribute = ?1spring-doc.cn

你也可以使用 表达式 来控制参数 也可以用来控制方法参数。 在这些表达式中,实体名称不可用,但参数可用。 可以按名称或索引访问它们,如以下示例所示。spring-doc.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);

对于 -conditions,人们通常希望附加到 String 值参数的开头或结尾。 这可以通过在绑定参数标记或 SPEL 表达式前附加或作为其前缀来完成。 以下示例再次演示了这一点。like%%spring-doc.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);

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

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

给定此方法,存储库接口中的声明将找到,但不会找到。 使用的转义字符可以通过设置 annotation 来配置。 请注意,SPEL 上下文中可用的方法将仅转义 SQL 和 JPQL 标准通配符和 . 如果底层数据库或 JPA 实现支持其他通配符,则不会转义这些通配符。findContainingEscaped("Peter_")Peter_ParkerPeter ParkerescapeCharacter@EnableJpaRepositoriesescape(String)_%spring-doc.cn

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

如果您希望在运行时解析属性,您还可以在查询方法中引用配置属性名称,包括回退。 在执行查询时评估该属性。 通常,属性占位符解析为类似 String 的值。Environmentspring-doc.cn

其他方法

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

  • 如果您还没有,只需使用 @Query 自行编写查询即可。spring-doc.cn

  • 如果这不符合您的需求,请考虑实现自定义实现。这样,您就可以在存储库中注册方法,同时将实现完全留给您。这使您能够:spring-doc.cn

    • 直接与 (编写纯 HQL/JPQL/EQL/本机 SQL 或使用 Criteria API ) 交谈EntityManager)spring-doc.cn

    • 利用 Spring Framework 的(本机 SQL)JdbcTemplatespring-doc.cn

    • 使用另一个第三方数据库工具包。spring-doc.cn

  • 另一种选择是将查询放入数据库中,然后使用 Spring Data JPA 的 @StoredProcedure 注释,或者如果它是数据库函数,则使用 @Query 注释并使用 .CALLspring-doc.cn

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

修改查询

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

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

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

注释仅与注释结合使用。 派生的查询方法或自定义方法不需要此注释。@Modifying@Queryspring-doc.cn

派生的删除查询

Spring Data JPA 还支持派生的删除查询,这使您不必显式声明 JPQL 查询,如以下示例所示:spring-doc.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);
}

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

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

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

应用查询提示

要将 JPA 查询提示应用于存储库界面中声明的查询,您可以使用注释。它需要一个 JPA 注释数组和一个布尔标志,才能潜在地禁用应用于应用分页时触发的其他 count 查询的提示,如以下示例所示:@QueryHints@QueryHintspring-doc.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);
}

前面的声明将应用为该实际查询配置的查询,但忽略将其应用于为计算总页数而触发的 count 查询。@QueryHintspring-doc.cn

向查询添加注释

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

为了简化此过程,您可以将自定义注释插入到几乎任何 JPA 操作中,无论是查询还是其他操作 通过应用注释。@Metaspring-doc.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);
}

此示例存储库混合了自定义查找器以及覆盖从 继承的操作。 无论哪种方式,注释都允许您添加 a ,该查询将在发送到数据库之前插入到查询中。JpaRepository@Metacommentspring-doc.cn

还需要注意的是,此功能不仅限于查询。它延伸到 和 操作。 虽然没有显示,但它也适用于某些操作。countexistsdeletespring-doc.cn

虽然我们已尝试在所有可能的范围内应用此功能,但底层的某些操作不支持注释。例如,明确记录为支持注释,但操作没有。EntityManagerentityManager.createQuery()entityManager.find()

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

激活 Hibernate 注释

要在 Hibernate 中激活查询注释,必须设置为 。hibernate.use_sql_commentstruespring-doc.cn

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

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

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

如果您有文件,则可以在其中应用它:persistence.xmlspring-doc.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.propertiesspring-doc.cn

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

要在 EclipseLink 中激活查询注释,必须设置为 。eclipselink.logging.level.sqlFINEspring-doc.cn

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

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

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

如果您有文件,则可以在其中应用它:persistence.xmlspring-doc.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.propertiesspring-doc.cn

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

配置 Fetch- 和 LoadGraph

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

以下示例演示如何在实体上定义命名实体图:spring-doc.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.cn

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

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

}

还可以使用 来定义 Ad Hoc 实体图 。提供的将转换为 根据,而无需显式添加到您的域类型,如以下示例所示:@EntityGraphattributePathsEntityGraph@NamedEntityGraphspring-doc.cn

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

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

}

滚动

滚动是一种更精细的方法,用于迭代较大的结果集块。 滚动包括稳定排序、滚动类型(基于 Offset 或 Keyset 的滚动)和结果限制。 您可以使用属性名称定义简单的排序表达式,并通过查询派生使用 TopFirst 关键字定义静态结果限制。 您可以连接表达式以将多个条件收集到一个表达式中。spring-doc.cn

滚动查询返回 a,它允许获取元素的滚动位置以获取下一个,直到您的应用程序使用完整个查询结果。 与通过获取下一批结果来使用 Java 类似,查询结果滚动允许您访问 a 到 。Window<T>Window<T>Iterator<List<…>>ScrollPositionWindow.positionAt(…​)spring-doc.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());

用于标识元素与整个查询结果的确切位置。 查询执行将 position 参数视为 exclusive ,结果将在给定位置之后开始。 以及作为 a 的特殊化身,表示滚动操作的开始。ScrollPositionScrollPosition#offset()ScrollPosition#keyset()ScrollPositionspring-doc.cn

上面的示例显示了静态排序和限制。 您也可以定义接受对象的查询方法,定义更复杂的排序顺序或基于每个请求的排序。 同样,提供对象允许您基于每个请求定义动态限制,而不是应用静态限制。 在 Query Methods Details 中阅读有关动态排序和限制的更多信息。SortLimitspring-doc.cn

WindowIterator提供了一个实用程序,通过消除检查是否存在 next 并应用 .WindowWindowScrollPositionspring-doc.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.cn

例 34.与 Repository 查询方法一起使用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

和 之间存在差异。 前者表示滚动操作的开始,不指向特定的偏移量,而后者标识结果的第一个元素(在 position )。 鉴于滚动的独占性质,using 会跳过第一个元素并转换为 .ScollPosition.offset()ScollPosition.offset(0L)0ScollPosition.offset(0)1spring-doc.cn

使用 Keyset-Filtering 进行滚动

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

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

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

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

与 Repository 查询方法一起使用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 从头开始,不要应用其他筛选。

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

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

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