此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Data JPA 3.5.2! |
JPA 查询方法
本节介绍使用 Spring Data JPA 创建查询的各种方法。
查询查找策略
JPA 模块支持手动将查询定义为 String 或从方法名称派生它。
使用谓词的派生查询IsStartingWith
,StartingWith
,StartsWith
,IsEndingWith
,EndingWith
,EndsWith
,IsNotContaining
,NotContaining
,NotContains
,IsContaining
,Containing
,Contains
这些查询的相应参数将被清理。
这意味着如果参数实际上包含由LIKE
作为通配符,这些将被转义,因此它们仅作为文字匹配。
可以使用的转义字符通过设置escapeCharacter
的@EnableJpaRepositories
注解。
与使用值表达式进行比较。
声明的查询
尽管从方法名称派生的查询非常方便,但可能会面临这样的情况:方法名称解析器不支持要使用的关键字,或者方法名称会变得不必要地丑陋。因此,您可以通过命名约定使用 JPA 命名查询(有关更多信息,请参阅使用 JPA 命名查询),或者更确切地说,使用@Query
(参见用@Query
了解详情)。
查询创建
通常,JPA 的查询创建机制的工作原理是 查询方法 中所述的。以下示例显示了 JPA 查询方法转换为:
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 执行属性检查并遍历嵌套属性,如属性表达式中所述。
下表描述了 JPA 支持的关键字以及包含该关键字的方法转换为什么:
关键词 | 样本 | JPQL 代码片段 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
In 和NotIn 还采用Collection 作为参数以及数组或变量。对于同一逻辑运算符的其他语法版本,请检查存储库查询关键字。 |
然而,后一个查询会将焦点缩小到仅
无论如何,这个查询的意义何在?要找到具有给定姓氏的人数?要找到具有该绑定姓氏的不同人的数量?要找到不同姓氏的数量?(最后一个是一个完全不同的查询! 用 |
使用 JPA 命名查询
这些示例使用<named-query /> 元素和@NamedQuery 注解。必须在 JPA 查询语言中定义这些配置元素的查询。当然,您可以使用<named-native-query /> 或@NamedNativeQuery 太。这些元素允许您通过失去数据库平台独立性来定义本机 SQL 中的查询。 |
XML 命名查询定义
要使用 XML 配置,请添加必要的<named-query />
元素添加到orm.xml
JPA 配置文件位于META-INF
文件夹。使用一些定义的命名约定启用了命名查询的自动调用。有关更多详细信息,请参阅下文。
<named-query name="User.findByLastname">
<query>select u from User u where u.lastname = ?1</query>
</named-query>
查询具有一个特殊名称,用于在运行时解析它。
声明接口
要允许这些命名查询,请指定UserRepository
如下:
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByLastname(String lastname);
User findByEmailAddress(String emailAddress);
}
Spring Data 尝试将对这些方法的调用解析为命名查询,从配置的域类的简单名称开始,然后是用点分隔的方法名称。 因此,前面的示例将使用前面定义的命名查询,而不是尝试从方法名称创建查询。
用@Query
使用命名查询声明实体的查询是一种有效的方法,并且适用于少量查询。由于查询本身与运行它们的 Java 方法相关联,因此您实际上可以使用 Spring Data JPA 直接绑定它们@Query
注释,而不是将它们注释到域类。这将域类从持久性特定信息中解放出来,并将查询并位于存储库接口。
注释到查询方法的查询优先于使用@NamedQuery
或orm.xml
.
以下示例显示了使用@Query
注解:
@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
表达式,如以下示例所示:
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
模式。
本机查询
使用@NativeQuery
注释允许运行本机查询,如以下示例所示:
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 在代码中声明。
有关更多详细信息,请参阅下面的示例。 |
@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
后缀添加到查询的副本中。不过,您可能需要为计数查询注册结果集映射。
除了获取映射结果之外,本机查询还允许您读取原始结果Tuple
通过选择Map
container 作为方法的返回类型。
生成的映射包含表示实际数据库列名和值的键/值对。
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 结果支持Tuple s. |
基于字符串的元组查询仅受 Hibernate 支持。 Eclipselink 仅支持基于条件的元组查询。 |
查询自省和重写
Spring Data JPA 提供了广泛的功能,可用于运行各种类型的查询。 具体来说,给定一个声明的查询,Spring Data JPA 可以:
-
自省查询其投影,并运行接口投影的元组查询
-
如果查询使用构造函数表达式,请使用 DTO 投影,并在查询声明实体别名或仅声明多选表达式时重写投影
-
应用动态排序
-
派生一个
COUNT
查询
为此,我们附带了特定于 HQL(休眠)和 EQL(EclipseLink)方言的查询解析器,因为这些方言定义明确。
另一方面,SQL 允许方言之间存在相当大的差异。
因此,Spring Data 无法支持所有级别的查询复杂性。
我们不是通用的 SQL 解析器库,而是通过简化查询执行来提高开发人员生产力的库。
我们内置的 SQL 查询增强器仅支持用于自省的简单查询COUNT
查询派生。
更复杂的查询将需要使用 JSqlParser 或您提供COUNT
查询@Query(countQuery=…)
.
如果 JSqlParser 在类路径上,Spring Data JPA 将使用它进行本机查询。
要对选择进行细粒度控制,您可以配置QueryEnhancerSelector
用@EnableJpaRepositories
:
@Configuration
@EnableJpaRepositories(queryEnhancerSelector = MyQueryEnhancerSelector.class)
class ApplicationConfig {
// …
}
QueryEnhancerSelector
是一个策略接口,旨在选择QueryEnhancer
基于特定查询。
您也可以提供自己的QueryEnhancer
如果需要,可以实现。
应用 QueryRewriter
有时,无论您尝试应用多少功能,似乎都不可能让 Spring Data JPA 在将查询发送到EntityManager
.
您可以在查询发送到EntityManager
并“重写”它。
也就是说,您可以在最后一刻进行任何更改。
查询重写适用于实际查询,并在适用时应用于计数查询。
计数查询经过优化,因此,要么不需要,要么通过其他方式获得计数,例如从休眠SelectionQuery
如果有封闭交易。
@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。
您可以像这样编写查询重写器:
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
类。
另一种选择是让存储库本身实现接口。
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
,建议使用多个,每个都注册到应用程序上下文中。
在基于 CDI 的环境中,Spring Data JPA 将搜索BeanManager 对于实现QueryRewriter . |
使用排序
排序可以通过提供PageRequest
或通过使用Sort
径直。在Order
实例Sort
需要匹配您的域模型,这意味着它们需要解析为查询中使用的属性或别名。JPQL 将其定义为状态字段路径表达式。
使用任何不可引用的路径表达式都会导致Exception . |
但是,使用Sort
䋰@Query
让你潜入非路径检查Order
实例包含ORDER BY
第。这是可能的,因为Order
附加到给定的查询字符串中。默认情况下,Spring Data JPA 拒绝任何Order
实例包含函数调用,但您可以使用JpaSort.unsafe
添加潜在的不安全排序。
以下示例使用Sort
和JpaSort
,包括JpaSort
:
Sort
和JpaSort
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 指向别名函数的表达式。 |
滚动大型查询结果
处理大型数据集时,滚动有助于有效地处理这些结果,而无需将所有结果加载到内存中。
有多个选项来使用大型查询结果:
-
分页。 您在上一章中了解了
Pageable
和PageRequest
. -
基于偏移量的滚动。 这是比分页更轻的变体,因为它不需要总结果计数。
-
基于键集的滚动。 这种方法通过利用数据库索引避免了基于偏移量的结果检索的缺点。
阅读更多关于最适合您的特定安排的方法的信息。
可以将 Scroll API 与查询方法、Query-by-Example 和 Querydsl 一起使用。
尚不支持使用基于字符串的查询方法滚动。
也不支持使用 stored 滚动@Procedure 查询方法。 |
使用命名参数
默认情况下,Spring Data JPA 使用基于位置的参数绑定,如前面所有示例中所述。
这使得查询方法在重构参数位置时有点容易出错。
要解决此问题,您可以使用@Param
注释,为方法参数提供具体名称并在查询中绑定该名称,如以下示例所示:
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
.
运行查询时,将根据一组预定义的变量评估这些表达式。
如果您不熟悉值表达式,请参阅值表达式基础知识以了解 SpEL 表达式和属性占位符。 |
Spring Data JPA 支持一个名为entityName
.
它的用法是select x from #{#entityName} x
.
它插入entityName
与给定存储库关联的域类型。
这entityName
解决如下:
* 如果域类型在@Entity
注释,则使用它。
* 否则,使用域类型的简单类名。
以下示例演示了#{#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}
变量。
这entityName 可以使用@Entity 注解。
中的自定义orm.xml SpEL 表达式不支持。 |
当然,你可以直接使用User
直接在查询声明中,但这也需要您更改查询。
对#entityName
拾取未来可能的重新映射User
class 设置为不同的实体名称(例如,通过使用@Entity(name = "MyUser")
.
另一个用例#{#entityName}
表达式是如果要为具体域类型定义具有专用存储库接口的通用存储库接口。
若要不在具体接口上重复自定义查询方法的定义,可以在@Query
annotation,如以下示例所示:
@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> { … }
在前面的示例中,MappedTypeRepository
interface 是一些扩展的域类型的通用父接口AbstractMappedType
.
它还定义了通用findAllByAttribute(…)
方法,可用于专用存储库接口的实例。
如果您现在调用findAllByAttribute(…)
上ConcreteRepository
,则查询将变为select t from ConcreteType t where t.attribute = ?1
.
您还可以使用表达式来控制参数,也可以用于控制方法参数。 在这些表达式中,实体名称不可用,但参数可用。 可以通过名称或索引访问它们,如以下示例所示。
@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 表达式前附加或前缀 来完成。
同样,以下示例演示了这一点。%
%
@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 中可用,这允许轻松清理绑定参数。
@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 实现支持其他通配符,则这些通配符将不会被转义。_
%
@Query("select u from User u where u.applicationName = ?${spring.application.name:unknown}")
List<User> findContainingEscaped(String namePart);
如果您希望从Environment
在运行时。
在查询执行时对属性进行评估。
通常,属性占位符解析为类似字符串的值。
其他方法
Spring Data JPA 提供了许多构建查询的方法。 但有时,您的查询对于所提供的技术来说可能太复杂了。 在这种情况下,请考虑:
-
如果您还没有,只需使用
@Query
. -
如果这不符合您的需求,请考虑实现自定义实现。这使您可以在存储库中注册一个方法,同时将实现完全由您决定。这使您能够:
-
直接与
EntityManager
(编写纯 HQL/JPQL/EQL/原生 SQL 或使用 Criteria API) -
利用 Spring Framework 的
JdbcTemplate
(本机 SQL) -
使用另一个第三方数据库工具包。
-
-
另一种选择是将查询放入数据库中,然后使用 Spring Data JPA 的
@StoredProcedure
注解或者如果它是使用@Query
注解并使用CALL
.
当您需要最大限度地控制查询,同时仍然让 Spring Data JPA 提供资源管理时,这些策略可能是最有效的。
修改查询
前面的所有部分都介绍了如何声明查询以访问给定实体或实体集合。
您可以使用 Spring Data Repositories 的自定义实现中描述的自定义方法工具来添加自定义修改行为。
由于这种方法对于全面的自定义功能是可行的,因此您可以通过使用@Modifying
,如以下示例所示:
@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
.
这@Modifying
注释仅与@Query
注解。
派生查询方法或自定义方法不需要此注解。
派生删除查询
Spring Data JPA还支持派生删除查询,使您不必显式声明JPQL查询,如以下示例所示:
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
看不到调用的生命周期回调。
为确保实际调用生命周期查询,请调用deleteByRoleId(…)
运行查询,然后逐个删除返回的实例,以便持久性提供程序可以实际调用@PreRemove
这些实体的回调。
事实上,派生的删除查询是运行查询然后调用CrudRepository.delete(Iterable<User> users)
在结果上保持行为与其他delete(…)
方法CrudRepository
.
删除大量对象时,您需要考虑性能影响,以确保足够的内存可用性。所有生成的对象在被删除之前都会加载到内存中,并保留在会话中,直到刷新或完成事务。 |
应用查询提示
要将 JPA 查询提示应用于存储库接口中声明的查询,您可以使用@QueryHints
注解。它采用 JPA 数组@QueryHint
注释加上布尔标志,以可能禁用应用于应用分页时触发的其他计数查询的提示,如以下示例所示:
public interface UserRepository extends Repository<User, Long> {
@QueryHints(value = { @QueryHint(name = "name", value = "value")},
forCounting = false)
Page<User> findByLastname(String lastname, Pageable pageable);
}
前面的声明将应用配置的@QueryHint
对于该实际查询,但省略将其应用于触发的计数查询以计算总页数。
向查询添加注释
有时,您需要根据数据库性能调试查询。
数据库管理员向您显示的查询可能看起来与您使用的内容非常不同@Query
,或者它可能看起来
与您假设的 Spring Data JPA 生成的有关自定义查找器或您使用示例查询的内容完全不同。
为了使此过程更容易,您可以在几乎任何 JPA作中插入自定义注释,无论是查询还是其他作
通过应用@Meta
注解。
@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
在将查询发送到数据库之前,它们将入到查询中。
同样重要的是要注意,此功能不仅限于查询。它扩展到count
和exists
操作。
虽然没有显示,但它也扩展到某些delete
操作。
虽然我们尝试在所有可能的地方应用此功能,但底层EntityManager 不支持评论。例如entityManager.createQuery() 被明确记录为支持评论,但是entityManager.find() 作则不然。 |
JPQL 日志记录和 SQL 日志记录都不是 JPA 中的标准,因此每个提供程序都需要自定义配置,如下图所示。
激活休眠注释
要在 Hibernate 中激活查询注释,您必须将hibernate.use_sql_comments
自true
.
如果您使用的是基于 Java 的配置设置,则可以这样完成:
@Bean
public Properties jpaProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.use_sql_comments", "true");
return properties;
}
如果你有一个persistence.xml
文件,您可以在那里应用它:
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.jpa.properties.hibernate.use_sql_comments=true
激活 EclipseLink 注释
要在 EclipseLink 中激活查询注释,您必须将eclipselink.logging.level.sql
自FINE
.
如果您使用的是基于 Java 的配置设置,则可以这样完成:
@Bean
public Properties jpaProperties() {
Properties properties = new Properties();
properties.setProperty("eclipselink.logging.level.sql", "FINE");
return properties;
}
如果你有一个persistence.xml
文件,您可以在那里应用它:
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.jpa.properties.eclipselink.logging.level.sql=FINE
配置 Fetch- 和 LoadGraphs
JPA 2.1 规范引入了对指定 Fetch- 和 LoadGraphs 的支持,我们还支持@EntityGraph
注释,它允许您引用@NamedEntityGraph
定义。您可以在实体上使用该注释来配置生成查询的提取计划。类型 (Fetch
或Load
) 可以通过使用type
属性@EntityGraph
注解。有关进一步的参考,请参阅 JPA 2.1 规范 3.7.4。
以下示例演示如何在实体上定义命名实体图:
@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {
// default fetch mode is lazy.
@ManyToMany
List<GroupMember> members = new ArrayList<GroupMember>();
…
}
以下示例演示如何在存储库查询方法上引用命名实体图:
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name);
}
还可以使用@EntityGraph
.提供的attributePaths
被翻译成EntityGraph
无需显式添加@NamedEntityGraph
添加到您的域类型,如以下示例所示:
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
滚动
滚动是一种更细粒度的方法,用于迭代更大的结果集块。
滚动由稳定排序、滚动类型(基于偏移量或键集的滚动)和结果限制组成。
您可以使用属性名称定义简单的排序表达式,并使用Top
或First
关键词通过查询派生。
您可以连接表达式以将多个条件收集到一个表达式中。
滚动查询返回Window<T>
允许获取元素的滚动位置以获取下一个Window<T>
直到您的应用程序使用了整个查询结果。
类似于使用 JavaIterator<List<…>>
通过获取下一批结果,查询结果滚动允许您访问ScrollPosition
通过Window.positionAt(…)
.
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
提供了一个实用程序来简化滚动Window
s 通过消除检查是否存在下一个Window
并应用ScrollPosition
.
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 开始的结果。 这种简单的机制避免了将大量结果发送到客户端应用程序。 但是,大多数数据库需要在服务器返回结果之前具体化完整的查询结果。
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 . |
之间有区别 |
使用键集过滤滚动
基于偏移量要求大多数数据库需要具体化整个结果,然后服务器才能返回结果。 因此,虽然客户端只能看到请求结果的一部分,但您的服务器需要构建完整的结果,这会导致额外的负载。
键集过滤通过利用数据库的内置功能来检索结果子集,旨在减少单个查询的计算和 I/O 要求。 此方法维护一组键,通过将键传递到查询中来恢复滚动,从而有效地修改筛选条件。
Keyset-Filtering 的核心思想是使用稳定的排序顺序开始检索结果。
一旦你想滚动到下一个块,你就会得到一个ScrollPosition
用于重建排序结果中的位置。
这ScrollPosition
捕获当前Window
.
为了运行查询,重建会重写 criteria 子句以包含所有排序字段和主键,以便数据库可以利用潜在的索引来运行查询。
数据库只需要从给定的键集位置构造一个小得多的结果,而不需要完全具体化一个大结果,然后跳过结果,直到达到特定的偏移量。
键集过滤要求键集属性(用于排序的属性)不可为空。
此限制适用于特定于商店 |
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 | 从头开始,不要应用额外的过滤。 |
当您的数据库包含与排序字段匹配的索引时,键集过滤效果最佳,因此静态排序效果很好。 应用键集过滤的滚动查询需要查询返回排序顺序中使用的属性,并且这些属性必须映射到返回的实体中。
可以使用接口和 DTO 投影,但请确保包含排序依据的所有属性,以避免键集提取失败。
指定您的Sort
order,则包含与查询相关的排序属性就足够了;
如果您不想,则无需确保唯一的查询结果。
键集查询机制通过包含主键(或复合主键的任何其余部分)来修改排序顺序,以确保每个查询结果都是唯一的。