Spring Data JPA 定义查询方法说明

Spring Data JPA 如何利用方法名定义查询方法?

一、定义查询方法的配置方法

由于Spring JPA Repository的实现原理是采用动态代理的机制,所以我们介绍两种定义查询方法:

  1. 从方法名称中可以指定特定用于存储的查询和更新,
  2. 或通过使用@Query手动定义的查询,这个取决于实际存储操作。

只需要实体Repository继承Spring Data Common里面的Repository接口即可。

如果你想有其他更多默认通用方法的实现,可以选择JpaRepository、PagingAndSortingRepository、CrudRepository等接口,也可以直接继承我们后面要讲的JpaSpecificationExecutor、QueryByExampleExecutor和自定义Response,都可以达到同样的效果。

如果你不想扩展Spring数据接口,还可以使用它来注解存储库接口@RepositoryDefinition。扩展CrudRepository公开了一套完整的方法来操纵实体。如果你希望对所暴露的方法有选择性,只需要将暴露的方法复制CrudRepository到域库中即可。其实也是自定义Repository的一种。

看下面的示例,选择性地暴露CRUD方法:(findOne和save)

@NoRepositoryBeaninterface
MyBaseRepository<T, ID extends Serializable> extends Rep〇sit〇ry<T, ID> { 
T find〇ne(ID id);
T save(T entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}

在这个示例的第一步中为所有域存储库定义了一个公共基础接口,并将其暴露出来。findOne(…)和save(…)方法将被路由到由Spring Data提供的、你选择的存储库的基本存储库实现中。

例如JPA中的SimpleJpaRepository。因为它们正在匹配方法签名CrudRepository,所以UserRepository将能够保存用户,并通过id查找单个用户信息,以及触发查询以通过其电子邮件地址查找Users。

二、方法的查询策略设置

通过@EnableJpaRepositories(queryLookupStrategy=QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND)可以配置方法的查询策略,其中QueryLookupStrategy.Key的值一共有三个。

  1. CREATE:直接根据方法名进行创建。规则是根据方法名称的构造进行尝试,一般的方法是从方法名中删除给定的一组已知前缀,并解析该方法的其余部分。如果方法名不符合规则,启动的时候就会报异常。
  2. USE_DECLARED_QUERY:声明方式创建,即本书说的注解方式。启动的时候会尝试找到一个声明的查询,如果没有找到就将抛出一个异常。查询可以由某处注释或其他方法声明。
  3. CREATE_IF_NOT_FOUND:这个是默认的,以上两种方式的结合版。先用声明方式进行查找,如果没有找到与方法相匹配的查询,就用create的方法名创建规则创建一个查询。

除非有特殊需求,一般直接用默认的,不用管。配置示例如下

@EnableJpaRepositories(queryLookupStrategy= QueryLookupStrategy.Key.CREATE—IF—NOT—FOUND) 
public class ExamplelApplication {
public static void main(String[] args) {
SpringApplication.run(ExamplelApplication.class,args);
}

QueryLookupStrategy 是策略的定义接口,jpaQueryLookupStrategy 是具体策略的实现类。

三、查询方法的创建

内部基础架构中有个根据方法名的查询生成器机制,对于在存储库的实体上构建约束查询很有用。

该机制方法的前缀有find…By、read…By、query…By、count…By和get…By,从这些方法可以分析它的其余部分(实体里面的字段)。

引入子句可以包含其他表达式,例如在Distinct要创建的查询上设置不同的标志。

然而,第一个By作为分隔符来指示实际标准的开始。在一个非常基本的水平上,你可以定义实体性条件,并与它们串联(And和Or)。

用一句话概括,待查询功能的方法名由查询策略(关键字)、查询字段和一些限制性条件组成。在如下例子中,可以直接在controller里面进行调用以查看效果:

interface PersonRepository extends Repository<User, Long> {
// and的查询关系
List<User> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
//包含distinct去重、or的SQL语法
List<User> findDistinctPeopleByLastnameOrFirstname(Stringlastname, String firstname);
List<User> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
//根据lastname字段查询忽略大小写
List<User> findByLastnamelgnoreCase(String lastname);
//根据lastname和firstname查询equal并且忽略大小写
List<User> findByLastnameAndFirstnameAllIgnoreCase(Stringlastname, String firstname);
//对查询结果根据lastname排序
List<User> findByLastnameOrderByFirstnameAsc(String lastname);
List<User> findByLastnameOrderByFirstnameDesc(String lastname);
}

解析方法的实际结果取决于创建查询的持久性存储。但是,有一些常见的事项需要注意:

  1. 表达式通常是可以连接的运算符的属性遍历。你可以使用组合属性表达式AND和OR。你还可以将运算关键字Between、LessThan、GreaterThan、Like作为属性表达式。受支持的操作员可能因数据存储而异,因此请参阅官方参考文档的相应部分内容。
  2. 该方法解析器支持设置一个IgnoreCase标志个别特性(例如,findByLastnameIgnoreCase(…))或支持忽略大小写(通常是一个类型的所有属性为String的情况下,例如,findByLastnameAndFirstnameAllIgnoreCase(…))。是否支持忽略示例可能会因存储而异,因此请参阅参考文档中的相关章节,了解特定于场景的查询方法。
  3. 可以通过OrderBy在引用属性和提供排序方向(Asc或Desc)的查询方法中附加一个子句来应用静态排序。要创建支持动态排序的查询方法来影响查询结果。

四、关键字列表

注意,除了find的前缀之外,我们查看PartTree的源码,还有如

下几种前缀:

private static final String QUERY_PATTERN = ” find | read | get | query | stream’;  
private static final String COUNT_PATTERN = "count"; 
private static final String EXISTS_PATTERN = "exists"; 
private static final String DELETE_PATTERN = "delete | remove';

使用的时候要配合不同的返回结果进行使用,例如:

interface UserRepository extends CrudRepository<User, Long> { 
long countByLastname (String lastname) ;//查询,总数 
long deleteByLastname (String lastname) ; / / 根据一个字段进行删除操作 
List<User> removeByLastname(String lastname);
}

五、方法的查询策略的属性表达式

属性表达式(Property Expressions)只能引用托管(泛化)实体的直接属性,如前一个示例所示。

在查询创建时,你已经确保解析的属性是托管实体的属性。同时,还可以通过遍历嵌套属性定义约束。

假设一个Person实体对象里面有一个Address属性里面包含一个ZipCode属性。在这种情况下,方法名为:

List<Person> findByAddressZipCode(String zipCode);

创建及其查找的过程是:

  1. 解析算法首先将整个part(AddressZipCode)解释为属性,并使用该名称(uncapitalized)检查域类的属性。
  2. 如果算法成功,就使用该属性。
  3. 如果不是,就拆分右侧驼峰部分的信号源到头部和尾部,并试图找出相应的属性,在我们的例子中是AddressZip和Code。
  4. 如果算法找到一个具有头部的属性,那么它需要尾部,并从那里继续构建树,然后按照刚刚描述的方式将尾部分割。
  5. 如果第一个分割不匹配,就将分割点移动到左边(Address、ZipCode),然后继续。

虽然这在大多数情况下应该起作用,但是算法可能会选择错误的属性。假设Person类也有一个addressZip属性,该算法将在第一个分割轮中匹配,并且基本上会选择错误的属性,最后失败(因为该类型addressZip可能没有code属性)。

要解决这个歧义,可以在方法名称中手动定义遍历点,所以我们的方法名称最终会是:

List<Person> findByAddress_ZipCode(String zipCode);

当然Spring JPA里面是将下划线视为保留字符,但是强烈建议遵循标准Java命名约定(不使用属性名称中的下划线,而是使用骆驼示例)。命名属性的时候注意一下这个特性。

可以到PartTreeJpaQuery.class 查询相关的method的name 拆分和实现逻辑。

六、查询结果的处理

6.1、参数选择分页和排序(Pageable/Sort)

1.特定类型的参数,动态地将分页和排序应用于查询

示例:在查询方法中使用Pageable、Slice和Sort。

Page<User>find By Lastname(String lastname,Page able page able);
Slice<User>find By Lastname(String lastname,Page able page able);
List<User>find By Lastname(String lastname,Sort sort);
List<User>find By Lastname(String lastname,page able page able);

第一种方法允许将org.springframework.data.domain.Pageable实例传递给查询方法,以便动态地将分页添加到静态定义的查询中。

Page 知道可用的元素和页面的总数。它通过基础框架里面触发计数查询来计算总数。由于这可能是昂贵的,具体取决于所使用的场景,说白了,当用到Pageable的时候会默认执行一条cout语句。

而Slice的作用是,只知道是否有下一个Slice可用,不会执行count,所以当查询较大的结果集时,只知道数据是足够的就可以了,而且相关的业务场景也不用关心一共有多少页。

排序选项也通过Pageable实例处理。如果只需要排序,那么在org.springframework.data.domain.Sort参数中添加一个参数即可。正如你可以看到的,只返回一个List也是可能的。在这种情况下,Page将不会创建构建实际实例所需的附加元数据(这反过来意味着必须不被发布的附加计数查询),而仅仅是限制查询仅查找给定范围的实体。

2、限制查询结果

示例:在查询方法上加限制查询结果的关键字first和top。

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(Stringlastname,Pageable pageable);

查询方法的结果可以通过关键字来限制first或top,其可以被互换地使用。

可选的数值可以追加到顶部/第一个以指定要返回的最大结果大小。如果数字被省略,则假设结果大小为1。

限制表达式也支持Distinct关键字。此外,对于将结果集限制为一个实例的查询,支持将结果包装到一个实例中的Optional中。如果将分页或切片应用于限制查询分页(以及可用页数的计算),则在限制结果中应用。

6.2、查询结果的不同形式(List/Stream/Page/Future)

Page和List在上面的示例中都有涉及,下面介绍几种特殊的。

流式查询结果

可以通过使用Java 8 Stream 作为返回类型来逐步处理查询方法的结果,而不是简单地将查询结果包装在Stream数据存储中,特定的方法用于执行流。

异步查询结果

可以使用Spring的异步方法执行功能异步的存储库查询。这意味着方法将在调用时立即返回,并且实际的查询执行将发生在已提交给Spring TaskExecutor的任务中,比较适合定时任务的实际场景。

6.3、Projections对查询结果的扩展

Spring JPA对Projections扩展的支持是非常好的。从字面意思上理解就是映射,指的是和DB查询结果的字段映射关系。一般情况下,返回的字段和DB查询结果的字段是一一对应的,但有的时候,我们需要返回一些指定的字段,不需要全部返回,或者只返回一些复合型的字段,还要自己写逻辑。Spring Data正是考虑到了这一点,允许对专用返回类型进行建模,以便我们有更多的选择,将部分字段显示成视图对象。

原文 

https://cn-blogs.cn/archives/8452.html

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Spring Data JPA 定义查询方法说明

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址