JPA 查询方法
本节介绍使用 Spring Data JPA 创建查询的各种方法。
查询查找策略
JPA 模块支持手动将查询定义为 String 或将其从方法名称派生。
带有谓词的派生查询IsStartingWith
,StartingWith
,StartsWith
,IsEndingWith
,EndingWith
,EndsWith
,IsNotContaining
,NotContaining
,NotContains
,IsContaining
,Containing
,Contains
这些查询的相应参数将被清理。
这意味着,如果参数实际上包含LIKE
作为通配符,这些将被转义,因此它们仅作为 Literals 匹配。
使用的转义字符可以通过设置escapeCharacter
的@EnableJpaRepositories
注解。
与 Using Value Expressions 比较。
声明的查询
尽管从方法名称派生的查询非常方便,但可能会遇到以下情况:方法名称解析器不支持要使用的关键字,或者方法名称会变得不必要地丑陋。因此,您可以通过命名约定使用 JPA 命名查询(有关更多信息,请参阅使用 JPA 命名查询),或者使用@Query
(参见用@Query
了解详情)。
查询创建
通常,JPA 的查询创建机制的工作方式如 Query Methods 中所述。以下示例显示了 JPA 查询方法的转换内容:
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 执行属性检查并遍历嵌套属性,如属性表达式中所述。
下表描述了 JPA 支持的关键字以及包含该关键字的方法的转换内容:
关键词 | 样本 | JPQL 代码段 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
In 和NotIn 也可以获取Collection 作为参数以及数组或 varargs 进行。对于同一逻辑运算符的其他语法版本,请检查 Repository query keywords。 |
但是,后一个查询会将焦点缩小到仅
无论如何,这个查询的意义何在?要查找具有给定姓氏的人数?要查找具有该绑定姓氏的不同人员的数量?
要查找不同姓氏的数量?(最后一个是完全不同的查询!
用 |
使用 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
注解,而不是将它们注解到 Domain 类。这样可以将域类从持久性特定信息中解放出来,并将查询归置到存储库界面。
注释到 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);
}
应用 QueryRewriter
有时,无论你尝试应用多少功能,似乎都不可能让 Spring Data JPA 应用所有内容
您希望在将查询发送到EntityManager
.
您可以在查询发送到EntityManager
并 “重写” 它。那是
您可以在最后一刻进行任何更改。
@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
的注解,或者将其作为@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
,则建议有多个 Cookie,每个 Cookie 都向
应用程序上下文。
在基于 CDI 的环境中, Spring Data JPA 将搜索BeanManager 对于您的QueryRewriter . |
使用高级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
delimiter 字符 () 被识别,并且查询将转换为有效的 JPQL 查询(删除 )。运行查询时,传递给方法调用的参数会使用先前识别的%
%
LIKE
模式。
本机查询
使用@NativeQuery
annotation 允许运行本机查询,如以下示例所示:
public interface UserRepository extends JpaRepository<User, Long> {
@NativeQuery(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1")
User findByEmailAddress(String emailAddress);
}
这@NativeQuery annotation 主要是@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
suffix 添加到查询副本中。不过,您可能需要为 count 查询注册结果集映射。
除了获取映射结果之外,本机查询还允许您读取原始Tuple
从数据库中选择一个Map
container 作为方法的返回类型。
生成的 map 包含表示实际数据库列名称和值的键/值对。
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 result 由Tuple . |
2 | 倍数Map 结果支持Tuple s. |
基于字符串的元组查询仅受 Hibernate 支持。 Eclipselink 仅支持基于标准的元组查询。 |
使用 Sort
排序可以通过提供PageRequest
或使用Sort
径直。在Order
的实例Sort
需要匹配您的域模型,这意味着它们需要解析为查询中使用的属性或别名。JPQL 将其定义为状态字段路径表达式。
使用任何不可引用的 path 表达式都会导致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 expression 指向别名函数。 |
滚动大型查询结果
使用命名参数
默认情况下,Spring Data JPA 使用基于位置的参数绑定,如前面的所有示例所述。
这使得查询方法在重构参数位置时有点容易出错。
要解决此问题,您可以使用@Param
annotation 为方法参数提供具体名称并在查询中绑定该名称,如以下示例所示:
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 compiler 标志。通过在构建中使用此标志作为调试信息的替代方法,您可以省略@Param 注解。 |
使用表达式
我们支持在手动定义的查询中使用受限表达式,这些查询使用@Query
.
在运行查询时,将根据一组预定义的变量计算这些表达式。
如果您不熟悉值表达式,请参阅值表达式基础知识以了解 SPEL 表达式和属性占位符。 |
Spring Data JPA 支持一个名为entityName
.
它的用法是select x from #{#entityName} x
.
它会将entityName
的域类型。
这entityName
解析如下:
* 如果域类型在@Entity
annotation 时,会使用它。
* 否则,将使用域类型的简单 class-name。
以下示例演示了#{#entityName}
expression 在查询字符串中,您希望使用查询方法和手动定义的查询定义存储库接口:
@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
类设置为其他实体名称(例如,通过使用@Entity(name = "MyUser")
.
另一个用例#{#entityName}
expression 是如果要为具体域类型定义具有专用存储库接口的通用存储库接口。
要不在具体接口上重复自定义查询方法的定义,您可以在@Query
注解,如以下示例所示:
@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(…)
方法,该方法可以在专用存储库接口的实例上使用。
如果您现在调用findByAllAttribute(…)
上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
-conditions 通常希望附加到 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
-conditions 的值来自不安全的来源,则应清理这些值,以便它们不能包含任何通配符,从而允许攻击者选择比他们应该能够选择的数据更多的数据。
为此,escape(String)
方法在 SPEL 上下文中可用。
它用第二个参数中的单个字符作为第一个参数中 and 的所有实例的前缀。
与_
%
escape
子句的like
表达式,这允许轻松清理绑定参数。
@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 实现支持其他通配符,则不会转义这些通配符。_
%
@Query("select u from User u where u.applicationName = ?${spring.application.name:unknown}")
List<User> findContainingEscaped(String namePart);
如果您希望从Environment
在运行时。
在执行查询时评估该属性。
通常,属性占位符解析为类似 String 的值。
其他方法
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
可能包含过时的实体,我们不会自动清除它(参见 JavaDoc 的EntityManager.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 查询(在 annotation 中定义的那个)。
这意味着,即使是当前加载的User
看不到调用的生命周期回调。
为了确保生命周期查询被实际调用,对deleteByRoleId(…)
运行查询,然后逐个删除返回的实例,以便持久性提供程序可以实际调用@PreRemove
回调。
实际上,派生的 delete 查询是运行查询,然后调用CrudRepository.delete(Iterable<User> users)
并保持行为与其他delete(…)
methods 中的CrudRepository
.
应用查询提示
要将 JPA 查询提示应用于存储库接口中声明的查询,您可以使用@QueryHints
注解。它需要一个 JPA 数组@QueryHint
annotations 加上一个布尔标志,以可能禁用应用于应用分页时触发的其他 Count 查询的提示,如以下示例所示:
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 查询,以计算总页数。
向查询添加注释
有时,您需要根据数据库性能调试查询。
您的数据库管理员显示的查询可能看起来与您编写的查询非常不同@Query
,或者它可能看起来
与您假定的 Spring Data JPA 生成的有关自定义查找器的内容或您使用 query by example 完全不同。
为了简化此过程,您可以将自定义注释插入到几乎任何 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
annotation 允许您添加comment
,在将查询发送到数据库之前,这些查询将入到查询中。
还需要注意的是,此功能不仅限于查询。它扩展到count
和exists
操作。
虽然没有显示,但它也延伸到某些delete
操作。
虽然我们尝试在所有可能的地方应用此功能,但底层EntityManager 不支持评论。例如entityManager.createQuery() 被明确记录为支持评论,但entityManager.find() 作则不会。 |
JPQL 日志记录和 SQL 日志记录都不是 JPA 中的标准,因此每个提供程序都需要自定义配置,如以下部分所示。
激活 Hibernate 注释
要在 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- 和 LoadGraph
JPA 2.1 规范引入了对指定 Fetch- 和 LoadGraphs 的支持,我们也通过@EntityGraph
注解,它允许您引用@NamedEntityGraph
定义。您可以在实体上使用该注释来配置结果查询的提取计划。类型 (Fetch
或Load
) 的 Git 可以通过使用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);
}
还可以使用 Ad Hoc 实体图@EntityGraph
.提供的attributePaths
被转换为相应的EntityGraph
而无需显式添加@NamedEntityGraph
添加到您的域类型,如以下示例所示:
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
滚动
滚动是一种更精细的方法,用于迭代较大的结果集块。
滚动包括稳定排序、滚动类型(基于 Offset 或 Keyset 的滚动)和结果限制。
您可以使用属性名称定义简单的排序表达式,并使用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,无需检查是否存在 nextWindow
并应用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 计数器来跳过许多结果,并让数据源仅返回从给定 Offset 开始的结果。 这种简单的机制可避免将大型结果发送到客户端应用程序。 但是,大多数数据库都需要在服务器返回结果之前具体化完整的查询结果。
OffsetScrollPosition
使用 Repository Query Methodsinterface 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 . |
两者之间有区别 |
使用 Keyset-Filtering 进行滚动
基于偏移量要求大多数数据库需要先具体化整个结果,然后服务器才能返回结果。 因此,虽然客户端只能看到请求结果的部分,但您的服务器需要构建完整的结果,这会导致额外的负载。
Keyset-Filtering 方法通过利用数据库的内置功能来生成子集检索,旨在减少单个查询的计算和 I/O 要求。 此方法通过将键传递到查询中来维护一组键以恢复滚动,从而有效地修改筛选条件。
Keyset-Filtering 的核心思想是使用稳定的排序顺序开始检索结果。
一旦你想滚动到下一个数据块,你会得到一个ScrollPosition
用于重建 Sorted 结果中的位置。
这ScrollPosition
捕获当前Window
.
为了运行查询,重建会重写 criteria 子句以包含所有排序字段和主键,以便数据库可以利用潜在索引来运行查询。
数据库只需要从给定的键集位置构造一个小得多的结果,而无需完全实现一个大的结果,然后跳过结果直到达到特定的偏移量。
Keyset-Filtering 要求键集属性(用于排序的属性)不可为空。
此限制适用,因为特定于 store |
KeysetScrollPosition
使用 Repository Query Methodsinterface 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 的滚动查询需要查询返回的排序顺序中使用的属性,并且这些属性必须映射到返回的实体中。
您可以使用接口和 DTO 投影,但请确保包含您排序所依据的所有属性,以避免键集提取失败。
指定Sort
order 中,包含与查询相关的 sort 属性就足够了;
如果您不想,则无需确保查询结果唯一。
键集查询机制通过包含主键(或复合主键的任何剩余部分)来修改排序顺序,以确保每个查询结果都是唯一的。