Spring JPA 定义查询方法

Spring JPA 定义查询方法

翻译:Defining Query Methodshtml

​ 存储库代理有两种方式基于方法名派生特定域的查询方式:java

  • 直接从方法名派生查询
  • 自定义查询方式

​ 可用选项基于实际存储。可是,必须有一个策略来决定建立什么样的实际查询。下一节将介绍可用的选项。算法

一、查询查找策略

​ 如下策略可用于存储库基础结构来解决查询。使用XML配置,能够经过querylookup strategy属性在名称空间配置策略。对于Java配置,可使用Enable${store}Repositories注释的queryLookupStrategy属性。但某些策略可能不支持特定的数据存储。spring

  • create查询方式尝试从查询方法名称构造特定于存储的查询。通常是删除从方法中删除不用的部分,而后细化用到的部分。你能够从Query-Creation了解更多关于查询建立的内容。
  • USE_DECLARED_QUERY尝试查找已声明的查询,若是找不到则引起异常。查询能够经过某个地方的注释进行定义,或经过其余方式进行声明。请参阅特定存储库方法的文档,以找到该存储库内的可用方法。若是存储库基础结构在引导时未找到方法的声明查询,则致使失败。
  • CREATE_IF_NOT_FOUND(默认)结合CREATEUSE_DECLARED_QUERY的查询。它首先查找已声明的查询,若是没有找到声明的查询,它将建立一个基于自定义方法名的查询。这是默认的查找策略,所以,若是未显式配置任何内容,则使用此策略。它容许经过方法名快速定义查询,还能够根据须要引入声明的查询来定制这些查询。

二、查询建立

​ Spring数据存储库基础方法中内置的查询生成器机制对于在存储库的实体上构建的约束查询很是有用。该机制从方法中剥离前缀find…By、read…By、query…By、count…Byget…By,并开始解析其他部分。引入子句能够包含更多的表达式,例如在要建立的查询上设置Distinct标志的Distinct。第一个By充当分隔符,指示实际条件的开始。您能够定义实体属性的条件,并将它们使用andOr链接起来。如下示例演示如何建立多个查询:编程

例13:从方法名建立查询api

interface PersonRepository extends Repository<Person, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // 容许去重查询
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // 容许忽略大小写查询
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // 容许查询结果进行排序
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

解析方法的实际结果取决因而基于哪一个持久类实体进行的查询建立,可是,也有一些通常性问题须要注意:安全

  • 表达式一般是属性字段和运算符组合在一块儿进行遍历,你可使用AND或者OR组合属性表达式,同时也支持Between, LessThan, GreaterThan, 和Like等运算符,支持的运算符可能因数据存储而异,具体请参考文档的相应部分。
  • 方法解析器支持为单个属性(例如findByLastnameIgnoreCase(…))或支持忽略大小写的类型的全部属性设置IgnoreCase标志(一般是字符串实例  ,例如findByLastnameAndFirstnameAllIgnoreCase(…))。是否支持忽略大小写可能因存储而异,所以请参阅参考文档中的相关部分以了解特定于存储的查询方法。
  • 经过向引用属性的查询方法追加OrderBy子句并提供排序方向(AscDesc),能够应用静态排序。要建立支持动态排序的查询方法,请参阅“特殊参数处理”。

三、属性表达式

​ 属性表达式只能引用实体类定义的直接属性,如上例所示,在建立查询时,你已经肯定属性是实体类对应域中的属性,除此以外,还能够经过嵌套属性定义约束。性能优化

List<Person> findByAddressZipCode(ZipCode zipCode);

​ 假定一我的拥有一个带邮政编码的地址,在这种状况下,该方法遍历建立属性x.address.zipCode. 解析算法首先将整个部分(AddressZipCode)解释为属性,而后在域类中检查具备该名称(未大写)的属性。若是算法成功,则使用该属性。若是不是这样,算法会把驼峰命名部分的源代码拆分,并尝试在咱们的示例中找到相应的属性 AddressZipCode。若是算法找到一个带有该头部的属性,它将获取尾部并继续从那里构建树,并按照刚才描述的方式将尾部拆分。若是第一个拆分不匹配,则算法将拆分点向左移动(Address、ZipCode)并继续。dom

举例说明拆分:AaBbCc异步

第一次拆分 AaBb / Cc 获取属性方式 AaBb.Cc

第二次拆分 Aa / BbCc 获取属性方式 Aa.BbCc

​ 尽管这在大多数状况下都是可行的,但算法仍然可能会选择错误的属性。假设Person类也有一个addressZip属性。该算法已经在第一轮分割中匹配,选择了错误的属性,而后就会失败(由于addressZip的类型可能没有代码属性)。

​ 要解决这种歧义,能够在方法名内部手动定义遍历点(以 - 定义遍历点)。

List<Person> findByAddress_ZipCode(ZipCode zipCode);

由于咱们将下划线字符视为保留字符,因此咱们强烈建议遵循标准的Java命名约定(即在属性名称中不使用下划线,而是使用驼峰大小写)。

四、特殊参数处理

​ 要处理查询中的参数,请像前面示例中所看到的那样定义方法参数。除此以外,基础结构还识别某些特定类型,如分页和排序,动态地对查询应用分页和排序。下面的示例演示了这些特性。

例14:在查询中使用Pageable, Slice, 和 Sort

Page<User> findByLastname(String lastname, Pageable pageable);

Slice<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Sort sort);

List<User> findByLastname(String lastname, Pageable pageable);

采用排序和可分页的api但愿将非空值传递给方法。若是不想应用任何排序或分页,可使用Sort.unsorted()和Pageable.unpaged()。

​ 第一个方法容许您传递一个org.springframework.data.domain查询方法的分页实例,以动态地向静态定义的查询添加分页。Page获取到了可用元素和页面的总数。它是经过基础结构触发计数查询来计算总数量来实现的。由于这可能会废算力(取决于所使用的存储),因此能够返回一个Slice。一个片只知道下一个片是否可用,这在遍历更大的结果集时可能就足够了。

TIPS:出于性能优化考虑,建议使用Slice

​ 排序一样经过Pageable实例进行处理,若是你只须要进行排序,只须要在你的方法中添加一个org.springframework.data.domain.Sort参数。如您所见,返回列表也是可能的。 在这种状况下,将不会建立构建Page实例所需的其余元数据(这意味着没有发出必要的附加计数查询)。相反,它将查询限制为仅查找给定范围的实体。

要查明整个查询获得了多少页,必须触发一个额外的count查询。默认状况下,该查询派生自您实际触发的查询。

可使用属性名定义简单的排序表达式。能够将表达式链接起来,将多个表达式整合到一个表达式中。

例15:定义查询表达式

Sort sort = Sort.by("firstname").ascending()
  .and(Sort.by("lastname").descending());

要以更类型安全的方式定义排序表达式,请从定义用于的排序表达式的类型开始,并使用方法引用定义要排序的属性

例16:使用类型安全的API定义排序表达式

TypedSort<Person> person = Sort.sort(Person.class);

Sort sort = person.by(Person::getFirstname).ascending()
  .and(person.by(Person::getLastname).descending());

TypedSort.by(…)一般经过使用CGlib来做为运行时代理,当使用Graal VM Native等工具时,CGlib可能会干扰本机映像的编译。

若是您的存储实现支持Querydsl,您还可使用生成的元模型类型来定义排序表达式。

例17: 使用Querydsl API定义排序表达式

QSort sort = QSort.by(QPerson.firstname.asc())
  .and(QSort.by(QPerson.lastname.desc()));

五、查询结果限制

查询结果可使用互换使用的top或者first关键字来进行限制,能够将一个可变的数字值附加到topfirst,以指定返回的最大结果大小。若是遗漏了这个数字,则使用默认值1。下面的示例显示如何限制查询大小。

例18:使用topfirst限制查询返回结果的大小

User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

Slice<User> findTop3ByLastname(String lastname, Pageable pageable);

List<User> findFirst10ByLastname(String lastname, Sort sort);

List<User> findTop10ByLastname(String lastname, Pageable pageable);

限制表达式还支持Distinct关键字。另外,对于将结果集限制为一个实例的查询,支持使用Optional关键字包装结果。

若是将分页或切片应用于限制查询分页(以及计算可用页面数量),则将其应用于有限的结果。

经过使用Sort参数来限制结果与动态排序的组合,能够表达最小和最大元素的查询方法。

六、返回集合或迭代的存储库方法

​ 返回多个结果的查询方法可使用标准的Java Iterable, List, Set。除此以外,咱们还支持返回Spring数据的Streamable, Iterable的自定义扩展,以及Vavr提供的集合类型。

例19:使用Streamable接收查询方法的结果

interface PersonRepository extends Repository<Person, Long> {
  Streamable<Person> findByFirstnameContaining(String firstname);
  Streamable<Person> findByLastnameContaining(String lastname);
}

Streamable<Person> result = repository.findByFirstnameContaining("av")
  .and(repository.findByLastnameContaining("ea"));

返回自定义可以使用Streamable包装的类型

​ 为集合提供专用的包装器类型是一种经常使用的模式,用于为返回多个元素的查询执行结果提供API。一般经过调用存储库方法返回类集合类型并手动建立包装器类型的实例来使用这些类型。能够避免这个额外的步骤,由于Spring Data容许使用这些包装器类型做为查询方法返回类型,若是它们知足如下标准:

  1. 该类型继承实现了Streamable
  2. 该类型公开名为of()valueOf()的构造函数或静态工厂方法,以Streamable做为参数。

用例以下所示:

class Product { 
    //产品实体公开访问价格的API
  MonetaryAmount getPrice() { … }
}

@RequiredArgConstructor(staticName = "of")
class Products implements Streamable<Product> { 
//可经过产品构造的Streamable<Product>的包装器类型。of()(经过Lombok注释建立的工厂方法)。
  private Streamable<Product> streamable;

  public MonetaryAmount getTotal() { 
    return streamable.stream() //包装器类型公开了在Streamable<Product>上计算新值的附加API。
      .map(Priced::getPrice)
      .reduce(Money.of(0), MonetaryAmount::add);
  }
}

interface ProductRepository implements Repository<Product, Long> {
    //包装器类型能够直接用做查询方法返回类型。不须要返回Stremable<Product>并手动将其封装到存储库客户机中。
  Products findAllByDescriptionContaining(String text); 
}

Vavr 集合的支持

Vavr是一个包含Java中函数式编程概念的库。它附带了一组可用做查询方法返回类型的自定义集合类型。

Vavr 集合类型 Vavr 实现的类型 Valid Java 源类型
io.vavr.collection.Seq io.vavr.collection.List java.util.Iterable
io.vavr.collection.Set io.vavr.collection.LinkedHashSet java.util.Iterable
io.vavr.collection.Map io.vavr.collection.LinkedHashMap java.util.Map

​ 第一列中的类型(或其子类型)能够用做查询方法返回类型,并将根据实际查询结果的Java类型(第三列)得到做为实现类型的第二列中的类型。而后经过实现派生类的方法进行类型转化。

七、空值方法处理库

​ 在Spring Data 2.0中,返回单个聚合实例的存储库CRUD方法使用Java 8 s可选来指示可能缺乏的值。除此以外,Spring Data还支持在查询方法上返回如下包装器类型:

  • com.google.common.base.Optional
  • scala.Option
  • io.vavr.control.Option

​ 或者,查询方法能够选择根本不使用包装器类型。若是没有查询结果,则返回null。返回集合、集合替代、包装器和流的存储库方法保证不会返回null,而是返回相应的空表示。有关详细信息,请参见存储库查询返回类型。

空值注解

您可使用Spring Framework的可空性注释来表示存储库方法的可空性约束。它们提供了一种工具友好的方法,并在运行时选择空检查,以下所示:

  • @NonNullApi:在包级别上使用,用于声明参数和返回值的默认行为是不接受或生成空值。
  • @NonNull:用于不能为null的参数或返回值(在@NonNullApi应用的地方,参数和返回值不须要)。
  • @Nullable:用于能够为空的参数或返回值。

Spring注释使用JSR 305注释(一种中止维护但普遍传播的JSR)进行元注释。JSR 305元注释让工具供应商(如IDEA、Eclipse和Kotlin)以通用的方式提供空安全支持,而没必要对Spring注释进行硬编码支持。要启用查询方法的nullability约束的运行时检查,您须要在package-info中使用Spring 的@NonNullApi来激活package-info.java上的非空配置,以下面的示例所示

例20:在包级别上声明非空

@org.springframework.lang.NonNullApi
package com.acme;

​ 一旦设置了非空默认值,存储库查询方法调用将在运行时验证是否存在可空性约束。若是查询执行结果违反定义的约束,则抛出异常。当方法将返回null,但声明为不可空时(存储库所在的包上定义的注释的默认值),就会发生这种状况。若是您但愿再次选择可为空的结果,能够在单个方法上有选择地使用@Nullable。使用本节开始提到的结果包装器类型继续按预期工做:空结果被转换为表示缺席的值。下面的示例显示了刚才描述的许多技术:

例21:使用不一样的空值配置

package com.acme;                                 // 存储库驻留在一个包(或子包)中,咱们为其定义了非空行为。                     

import org.springframework.lang.Nullable;

interface UserRepository extends Repository<User, Long> {

  User getByEmailAddress(EmailAddress emailAddress);      //当执行的查询不产生结果时,抛出EmptyResultDataAccessException。当传递给方法的电子邮件地址为空时,抛出IllegalArgumentException异常。              

  @Nullable
  User findByEmailAddress(@Nullable EmailAddress emailAdress);       //当执行的查询没有产生结果时,返回null。还接受null做为emailAddress的值。   

  Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); //当执行的查询不产生结果时,返回Optional.empty()。当传递给方法的电子邮件地址为空时,抛出IllegalArgumentException异常。
}

八、Stream化查询结果

​ 经过使用Java 8 Stream 做为返回类型,能够渐进地处理查询方法的结果。与将查询结果包装在流数据存储中不一样,使用特定的方法执行流,以下面的示例所示

例23:用Java 8 Stream<T流处理查询的结果

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

流可能包装了底层数据存储特定的资源,所以在使用后必须关闭。您可使用close()方法手动关闭流,也可使用Java 7 try-with-resources块,以下面的示例所示

例24:try-catch形式使用Stream

try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach(…);
}

9 、异步查询结果

​ 经过使用Spring的异步方法执行能力,存储库查询能够异步运行。这意味着,当实际的查询执行发生在已提交给Spring TaskExecutor的任务中时,该方法在调用时当即返回。异步查询执行与反应性查询执行不一样,不该该混合使用。有关响应性支持的更多细节,请参阅特定于存储的文档。下面的示例显示了许多异步查询

@Async
Future<User> findByFirstname(String firstname);               

@Async
CompletableFuture<User> findOneByFirstname(String firstname); 

@Async
ListenableFuture<User> findOneByLastname(String lastname);
相关文章
相关标签/搜索