JPA 查询方法

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

查询查询策略

JPA 模块支持手动将查询定义为字符串,或由方法名称导出查询。spring-doc.cadn.net.cn

带有谓词的派生查询IsStartingWith,开始,起始者,IsEndinging With,结尾,结束,IsNotContaining,非含蓄,非包含,即是包含,,包含这些问题的相关论点将被净化。 这意味着如果参数实际上包含被识别为喜欢作为万用符,这些会逃逸,所以它们只匹配字面值。 通过设置escapeCharacter关于@EnableJpaRepositories注解。 与“使用价值表达式”进行比较。spring-doc.cadn.net.cn

声明查询

虽然从方法名称获取查询非常方便,但可能会遇到方法名解析器不支持你想用的关键词,或者方法名称变得不必要地难看的情况。所以你可以通过命名规范使用 JPA 命名查询(详见 Using 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创建查询,并将其转换为以下查询:从 User u 中选择 u,其中 u.emailAddress = ?1 和 you.lastname = ?2.Spring Data JPA 进行属性检查并遍历嵌套属性,详见属性表达式spring-doc.cadn.net.cn

下表描述了JPA支持的关键词以及包含该关键词的方法的翻译:spring-doc.cadn.net.cn

表1。方法名中支持的关键词
关键词 样本 JPQL摘录

不同spring-doc.cadn.net.cn

通过姓氏和名字寻找Distinctspring-doc.cadn.net.cn

选择不同的......其中 x.姓氏 = ?1,x.firstname = ?2spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

查找ByLastname和Firstnamespring-doc.cadn.net.cn

…其中 x.姓氏 = ?1,x.firstname = ?2spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

通过姓氏或名字查找spring-doc.cadn.net.cn

…其中 x.姓氏 = ?1 或 x.firstname = ?2spring-doc.cadn.net.cn

,等于spring-doc.cadn.net.cn

查找按Firstname(查找名字),查找第一名是,findByFirstNameEqualsspring-doc.cadn.net.cn

…其中 x.firstname = ?1(或…其中 x.firstname 是 NULL如果参数为)spring-doc.cadn.net.cn

之间spring-doc.cadn.net.cn

查找开始日期之间spring-doc.cadn.net.cn

…其中 x.startDate 介于 ?1 和 ?2 之间spring-doc.cadn.net.cn

LessThanspring-doc.cadn.net.cn

findByAgeLessThanspring-doc.cadn.net.cn

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

不平等spring-doc.cadn.net.cn

查找ByAgeLessThanEqualspring-doc.cadn.net.cn

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

伟大超越spring-doc.cadn.net.cn

查找比年龄大于spring-doc.cadn.net.cn

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

比平等更伟大spring-doc.cadn.net.cn

查找比年龄大于平等spring-doc.cadn.net.cn

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

spring-doc.cadn.net.cn

查找ByStartDateAfterspring-doc.cadn.net.cn

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

以前spring-doc.cadn.net.cn

findByStartDateBeforespring-doc.cadn.net.cn

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

IsNull,spring-doc.cadn.net.cn

查找ByAge(Is)Nullspring-doc.cadn.net.cn

…其中 x.age 为零spring-doc.cadn.net.cn

IsNotNull,非零spring-doc.cadn.net.cn

查找ByAge(Is)NotNullspring-doc.cadn.net.cn

…其中 x.age 不是空spring-doc.cadn.net.cn

喜欢spring-doc.cadn.net.cn

findByFirstnameLikespring-doc.cadn.net.cn

…其中x.firstname像?1spring-doc.cadn.net.cn

不一样spring-doc.cadn.net.cn

findByFirstnameNotLike(查找第一名不喜欢)spring-doc.cadn.net.cn

…其中x.firstname不像?1spring-doc.cadn.net.cn

开始spring-doc.cadn.net.cn

查找由Firstname开始spring-doc.cadn.net.cn

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

结尾spring-doc.cadn.net.cn

以 EndingByFirstName 结尾spring-doc.cadn.net.cn

…其中x.firstname像?1(参数与前置的绑定%)spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

findByFirstnameContainingspring-doc.cadn.net.cn

…其中x.firstname像?1(参数绑定%)spring-doc.cadn.net.cn

OrderByspring-doc.cadn.net.cn

查找ByAgeOrderByLastnameDescspring-doc.cadn.net.cn

…其中x.age = ?1 按 x.姓氏 desc 排序spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

findByLastnameNotspring-doc.cadn.net.cn

…其中 x.lastname <> ?1(或…其中 x.lastname 不是空如果参数为)spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

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

…x.age 在 ?1 中spring-doc.cadn.net.cn

非罪spring-doc.cadn.net.cn

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

…其中 x.age 不在 ?1spring-doc.cadn.net.cn

truespring-doc.cadn.net.cn

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

…其中x.active = 真spring-doc.cadn.net.cn

falsespring-doc.cadn.net.cn

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

…其中x.active = falsespring-doc.cadn.net.cn

忽略Casespring-doc.cadn.net.cn

findByFirstname忽略Case。spring-doc.cadn.net.cn

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

非罪还取任意子类收集作为参数以及数组或变量。对于同一逻辑运算符的其他语法版本,请检查仓库查询关键词

不同这可能很棘手,而且并不总是能达到你预期的效果。 例如Choose distinct u from User u将得到完全不同的结果。从 User u 中选择不同的 u.lastname. 第一种情况,因为你包含了User.id,不会有重复,因此你会得到整个表,并且它将是用户对象。spring-doc.cadn.net.cn

不过,后者的问题会把重点缩小到仅限于某一User.lastname并找到该表中所有唯一的姓氏。 这也会产生一个List<String>结果集而非列表<用户>结果集。spring-doc.cadn.net.cn

countDistinctByLastname(字符串姓氏)也可能产生意想不到的结果。 春季数据JPA将推导Select count(distinct u.id) from User u where u.lastname = ?1. 再说一次,因为u.id不会遇到重复的,这个查询会统计所有拥有绑定姓氏的用户。 这与countByLastname(字符串姓氏)!spring-doc.cadn.net.cn

这个问题到底有什么意义?用来查找拥有某姓氏的人数量?要找出拥有那个绑定姓氏的不同人数? 是想找出不同姓氏的数量吗?(最后那个问题完全是另一个问题!) 用不同有时需要手动写查询,并使用@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 命名查询

示例中使用了<命名查询 />元素和@NamedQuery注解。这些配置元素的查询必须在JPA查询语言中定义。当然,你可以使用<命名-native-query />@NamedNativeQuery太。这些元素让你可以用本地 SQL 定义查询,从而失去数据库平台的独立性。

XML 命名查询定义

要使用 XML 配置,添加必要的<命名查询 />元素映射到orm.xmlJPA配置文件位于元步兵你的 classpath 文件夹。通过使用某种定义的命名惯例,可以实现命名查询的自动调用。更多详情请见下文。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

声明接口

为了允许这些命名查询,请指定用户仓库如下: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

注释到查询方法的查询优先于定义的查询@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);
}

使用 Advanced喜欢表达 式

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

例子6。高深喜欢表达式@Query
public interface UserRepository extends JpaRepository<User, Long> {

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

在前面的例子中,喜欢识别分隔符字符(),查询被转换为有效的JPQL查询(去除 )。在执行查询时,传递给方法调用的参数会被增强为之前识别的%%喜欢模式。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 在类路径上,或者计数查询在你的代码中宣告。 更多细节请参见下方示例。
例子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);
}

类似的方法也适用于命名的本地查询,通过添加。计数在你的查询副本后加上后缀。不过你可能需要为计数查询注册一个结果集映射。spring-doc.cadn.net.cn

除了获取映射结果外,原生查询还允许你读取原始数据通过选择地图container 作为方法的返回类型。 最终生成的映射包含代表实际数据库列名和值的键值对。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 地图结果以 a 为支持.
2 倍数地图结果支持s.
基于字符串的元组查询仅由 Hibernate 支持。 Eclipselink 仅支持基于条件的元组查询。

查询内省与重写

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

为此,我们配备了专门针对 HQL(Hibernate)和 EQL(EclipseLink)方言的查询解析器,因为这些方言定义清晰。 而SQL则允许不同方言之间有相当大的差异。 因此,Spring Data 永远无法支持所有级别的查询复杂度。 我们不是通用的SQL解析器库,而是通过简化查询执行来提升开发者生产力的库。 我们内置的SQL查询增强器只支持简单的内省查询计数查询推导。 更复杂的查询则需要使用 JSqlParser 或提供计数查询@Query(countQuery=...). 如果 JSqlParser 在类路径上,Spring Data JPA 会用它进行本地查询。spring-doc.cadn.net.cn

为了对选择进行细致控制,你可以配置QueryEnhancerSelector@EnableJpaRepositories:spring-doc.cadn.net.cn

例子10。Spring Data JPA 仓库使用 JavaConfig
@Configuration
@EnableJpaRepositories(queryEnhancerSelector = MyQueryEnhancerSelector.class)
class ApplicationConfig {
  // …
}

QueryEnhancerSelector是一个旨在选择QueryEnhancer基于具体的查询。 你也可以提供自己的QueryEnhancer如果你愿意,可以实现。spring-doc.cadn.net.cn

应用QueryRewriter

有时候,无论你尝试应用多少功能,似乎都不可能让 Spring Data JPA 在查询发送到实体管理器.spring-doc.cadn.net.cn

你可以在查询信发送给实体管理器然后“重写”它。 也就是说,你可以在最后一刻做任何修改。 查询重写适用于实际查询,并在适用时计入查询数。 计数查询经过优化,因此要么不需要计数,要么通过其他方式获得计数,比如从休眠中导出SelectionQuery(选择查询)如果存在附带交易。spring-doc.cadn.net.cn

例子11。请用 QueryRewriter 声明@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 会寻找在对应类型应用上下文中注册的豆子。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基于注释,或者将其作为@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将搜索豆经理对于你实现的实例QueryRewriter.

使用 Sort

排序可以通过提供以下条件来完成页面请求或者通过使用排序径直。实际使用的属性次序实例排序需要匹配你的域名模型,这意味着他们需要解析到查询中使用的属性或别名。JPQL将其定义为状态场路径表达式。spring-doc.cadn.net.cn

使用任意不可引用的路径表达式都会导致例外.

然而,使用排序@Query让你可以不经过路径检查地潜入次序包含顺序第。 这是可能的,因为次序附加在给定的查询字符串后。默认情况下,Spring Data JPA 拒绝任何次序包含函数调用的实例,但你可以使用JpaSort.unsafe还要增加可能不安全的点餐。spring-doc.cadn.net.cn

以下示例使用排序JpaSort,包括在 中的一个不安全选项JpaSort:spring-doc.cadn.net.cn

例子14。 用排序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 有效排序指向领域模型中属性的表达式。
2 无效排序包含函数调用。抛出异常。
3 有效排序包含明确不安全的内容 次序.
4 有效排序指向别名函数的表达式。

JpaSort.unsafe(...)限制

JpaSort.unsafe(...)工作模式有两种:spring-doc.cadn.net.cn

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

  • 当与示例查询或规范查询(使用标准查询),顺序表达式被解析并添加到标准查询作为表达式。查询表达式可以包含函数调用、各种子句(例如情形,算术表达式)或属性路径。顺序平移不支持子查询表达式,治疗.`spring-doc.cadn.net.cn

滚动大查询结果

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

你有多种方式可以处理大型查询结果:spring-doc.cadn.net.cn

  1. 呼叫。你在上一章已经学到了可页面页面请求.spring-doc.cadn.net.cn

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

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

阅读更多关于最适合你具体安排的方法spring-doc.cadn.net.cn

你可以使用滚动 API,配合查询方法、示例查询Querydslspring-doc.cadn.net.cn

目前还不支持基于字符串的查询方法进行滚动。使用存储查询时也不支持滚动@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完全支持基于-参数编译器标志。通过在构建中使用该标志作为调试信息的替代方案,你可以省略@Param命名参数的注释。

模板查询与表达式

我们支持在手动定义的查询中使用受限表达式,定义为@Query. 查询执行时,这些表达式会针对预定义的变量集合进行评估。spring-doc.cadn.net.cn

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

Spring Data JPA 支持一个名为实体名称. 其用法为从#{#entityName} X中选择X. 它插入了实体名称与该仓库相关的域类型。 这实体名称其解析如下:* 如果域类型在@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

实体名称可以通过使用@Entity注解。 自定义功能orm.xml不支持 SpEL 表达式。

当然,你也可以直接用用户直接在查询声明中,但那样也需要你修改查询。 对#entityName捕捉未来可能的重新映射用户类将 class 转换为不同的实体名称(例如,使用@Entity(name = “MyUser”).spring-doc.cadn.net.cn

另一个用例是#{#entityName}查询字符串中的表达式是指你想定义一个通用的仓库接口,并针对具体的领域类型专门的仓库接口。 为了避免在具体接口上重复自定义查询方法的定义,你可以在查询字符串中使用实体名称表达式@Query通用仓库界面中的注释,如下示例所示: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> { … }

在前面的例子中,映射类型仓库接口是少数扩展域类型的共同父接口AbstractMapTyp. 它也定义了通用药findAllByAttribute(...)方法,可用于专用仓库接口的实例。 如果你现在行使findAllByAttribute(...)ConcreteRepository,查询变为从 ConcreteType t 中选择 t 且 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);

喜欢- 通常希望附加在字符串值参数的开头或结尾的条件。 这可以通过附加或前缀绑定参数标记或 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);

使用喜欢-条件,且这些值来自非安全来源,这些值应被净化,使其无法包含任何通配符,从而允许攻击者选择比应有更多的数据。 为此逃逸(字符串)方法在 SpEL 上下文中可用。 它在第一个参数中的所有实例和前缀,加上第二个参数中的单个字符。 结合_%该条款喜欢表达式可在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但又不是彼得·帕克. 通过设置escapeCharacter关于@EnableJpaRepositories注解。 注意该方法逃逸(字符串)在 SpEL 上下文中可用的 只能逃避 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);

如果你想从以下处理某个属性,也可以在查询方法中引用配置属性名称,包括备用环境在运行时间内。 该属性在查询执行时被评估。 通常,属性占位符会解析为类似字符串的值。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);

这样做会触发该方法注释的查询作为更新查询,而非选择查询。作为实体管理器在修改查询执行后可能包含过时的实体,我们不会自动清除(参见 JavaDocEntityManager.clear()详情),因为这实际上会剔除所有未清除的更改,这些更改仍在实体管理器. 如果你愿意实体管理器要自动清除,可以设置@Modifying注释自动清除归属为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查询(即注释中定义的查询)。 这意味着即使是当前加载的用户不要看到生命周期回调被调用。spring-doc.cadn.net.cn

为了确保实际调用生命周期查询,调用deleteByRoleId(...)运行查询后逐个删除返回的实例,以便持久化提供者能够调用@PreRemove对这些实体进行回访。spring-doc.cadn.net.cn

事实上,派生删除查询就是运行查询后调用的快捷方式CrudRepository.delete(Iterable<User> users)在结果上,并保持行为与其他实现的同步删除(...)方法原油仓库.spring-doc.cadn.net.cn

在删除大量对象时,你需要考虑性能影响,以确保内存充足。 所有生成的对象在删除前都会加载到内存中,并一直保留在会话中,直到清除或完成事务。

应用查询提示

要对仓库界面中声明的查询应用JPA查询提示,可以使用@QueryHints注解。它需要一个JPA数组@QueryHint注释加上一个布尔标志,以可能禁用应用分页时触发的额外计数查询的提示,如下示例所示:spring-doc.cadn.net.cn

例子24。使用QueryHints配合repository方法的方法
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注释可以让你添加一个评论这些信息会在查询发送到数据库之前插入。spring-doc.cadn.net.cn

还需要注意的是,这个功能不仅限于查询。它延伸至计数存在操作。 虽然没有展示,但也延伸到了某些删除操作。spring-doc.cadn.net.cn

虽然我们尝试在所有可能的地方应用这一特性,但底层的一些作实体管理器不支持评论。例如entityManager.createQuery()明确作为支持性评论被记录,但entityManager.find()作则不然。

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

激活休眠评论

要在休眠中激活查询评论,您必须设置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.sql.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

配置取图和加载图

JPA 2.1规范引入了对指定取图和加载图的支持,我们也支持这些图与@EntityGraph注释,可以让你引用@NamedEntityGraph定义。你可以在实体上使用该注释来配置结果查询的取指计划。类型(获取负荷可通过使用类型属性@EntityGraph注解。详见JPA 2.1 Spec 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被翻译为实体图无需明确添加@NamedEntityGraph如下示例所示:spring-doc.cadn.net.cn

例子34。在仓库查询方法中使用临时实体图定义
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

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

}

滚动

滚动是一种更细粒度的方式,用于遍历较大的结果集块。 滚动包括稳定排序、一种滚动类型(基于偏移或键集的滚动)和结果限制。 你可以用属性名称定义简单的排序表达式,并用返回页首第一关键词通过查询推导。 你可以将表达式串接起来,将多个标准汇集成一个表达式。spring-doc.cadn.net.cn

滚动查询返回窗<T>这使得可以获得该元素的滚动位置以获取下一个窗<T>直到你的应用程序消耗了整个查询结果。 类似于消费 Java迭代器<列表<......>>通过获取下一批结果,查询结果滚动可以访问 a卷轴位置通过Window.positionAt(...),如下例所示:spring-doc.cadn.net.cn

Window<User> users = repository.findFirst10ByLastnameOrderByFirstname("Doe", ScrollPosition.offset());
do {

  for (User u : users) {
    // consume the user
  }

  if (users.isLast() || users.isEmpty()) {
    break;
  }

  // obtain the next Scroll
  users = repository.findFirst10ByLastnameOrderByFirstname("Doe", users.positionAt(users.size() - 1));
} while (!users.isEmpty());

卷轴位置将元素的精确位置与整个查询结果区分。 查询执行时,位置参数是专用的,结果会在给定位置之后开始ScrollPosition#offset()ScrollPosition#keyset()作为A的特殊化身卷轴位置表示滚动作的开始。spring-doc.cadn.net.cn

上面的例子展示了静态排序和限制。 你也可以定义接受排序对象定义了更复杂的排序顺序,或按请求排序。 以类似的方式,提供限制对象允许你按每个请求定义动态限制,而不是应用静态限制。 在查询方法详情中了解更多关于动态排序和限制的内容。spring-doc.cadn.net.cn

浏览消费实例需要相当多的条件句才能达到最佳的数据库往返,且很快会变成重复性任务,但可以通过以下方式简化WindowIterator.spring-doc.cadn.net.cn

WindowIterator提供了简化滚动的工具通过消除检查下一个存在的需求并应用卷轴位置.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
}

使用偏移进行滚动

偏移滚动使用类似于分页的偏移计数器,跳过多个结果,让数据源只返回从给定偏移开始的结果。 这一简单机制避免了向客户端应用程序发送大量结果。 然而,大多数数据库需要先实现完整的查询结果,服务器才能返回结果。spring-doc.cadn.net.cn

例子35。用偏移滚动位置使用仓库查询方法
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

键集过滤通过利用数据库内置功能实现结果子集检索,旨在减少单个查询的计算和输入输出需求。 这种方法通过将键传递到查询中,维持一组键来恢复滚动,从而有效修改你的筛选条件。spring-doc.cadn.net.cn

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

Keyset-Filtering 要求密钥集属性(用于排序的属性)必须是不可空的。 这个限制是因为具体商店的规定比较算子的值处理以及对索引源执行查询的需求。 对可空属性进行键集过滤会导致意想不到的结果。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

在指定你的排序顺序,只需包含与查询相关的排序属性即可; 如果你不想确保独特的查询结果,也不必如此。 键集查询机制通过包含主键(或复合主键的剩余部分)来调整排序顺序,确保每个查询结果都是唯一的。spring-doc.cadn.net.cn