Spring Data JPA - 查询创建(2)基于函数名

作者简介

陈喆,现就职于中科院某研究所担任副研究员,专注于工业云平台、MES系统的设计与研发。

内容来源:https://docs.spring.io/spring-data/jpa/docs/2.0.9.RELEASE/reference/html/#repositories.query-methods.details

有两种方法可以实现通过方法名称进行特定库的查询:

  • 通过方法名称直接生成查询
  • 通过人工定义查询

1. 查询查找策略

当使用XML配置时,可以通过query-lookup-strategy属性配置策略。当使用Java配置时,可以使用Enable${store}Repositories注解的queryLookupStrategy属性。一些策略可能在特定的数据库中不适用。

  • CREATE 试图根据查询方法名创建查询。一般的方法是从方法名移除已知前缀然后再解析剩余部分。
  • USE_DECLARED_QUERY试图查找一个实现声明的查询,如果找不到会抛出异常。可以通过注解或其它方法定义查询。
  • CREATE_IF_NOT_FOUND(默认)合并CREATEUSE_DECLARED_QUERY。它会首先查找声明的查询,如果查找不到,再基于方法命名创建自定义查询。如果没有显示定义策略,这将是默认策略。

2. 创建查询

Spring Data repository架构内建的查询构建机制可以实现基于repository的实体构建限制查询。该机制首先会从方法名剥离find…By, read…By, query…By, count…Byget…By等前缀,然后再解析剩余部分。引入的子句可以包含一些额外的表达式,比如Distinct用于在创建查询时设置唯一标识。By表示从此开始作为实际查询条件。你也可以通过AndOr将不同实体属性组合在一起,构成组合查询。

下面是通过方法名称创建查询的例子:

interface PersonRepository extends Repository<User, Long> {

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

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

实际解析的结果取决于创建查询时依赖的持久性存储。然后,需要注意一下几点共同特征:

  • 函数表达式是通过连接符将属性连接起来的。你可以使用AND和OR整合属性表达式,也可以使用Betwen,LessThan,GreaterThan和Like。不同数据库支持的连接符不同,需要查询相应的参考文献。
  • 支持给独立属性设置IgnoreCase标记(例如,findByLastnameIgnoreCase(…))或者给所有支持忽略大小写的属性设置IgnoreCase标记(例如:findByLastnameAndFirstnameAllIgnoreCase(…))。是否支持忽略大小写,不同数据库也不同,需要查询相应参考文献。
  • 你可以通过添加OrderBy子句实现静态排序,并且在要排序的属性后面设置排序方向(Asc或者Desc)。如果要实现动态排序,查阅“Special parameter handling”.

3. 属性表达式

属性表达式只能用于实体的直接属性。在创建查询时,你需要确定解析的属性就是实体类的属性。然而,你也可以定义遍历内嵌属性的约束。看一下下面的方法声明:

List<Person> findByAddressZipCode(ZipCode zipCode);

假设一个Person拥有一个带有ZipCode的Address. 在这种情况下,会按照x.address.zipCode作属性遍历。该算法首先会将整个部分(AddressZipCode)当作一个属性并到实体类中检查是否具有该名称的属性。如果查找到了,则使用该属性。如果没有查找到,算法会按照camel大小写将字符串从右至左作拆分,将右侧部分放到头部,剩余部分放到尾部,在本例中会拆分成AddressZip和Code.如果找到了头部对应的属性,就继续对尾部进行解析。如果没找到匹配的属性,会将分割点左移一个(Address,ZipCode),然后继续解析。

这种方式在绝大多数场景都适用,但也有可能选择到错误的属性。假设Person还有一个addressZip属性。解析算法会在第一次分割时匹配到属性,但并不是想要的Address属性,然后失败(因为没有code属性)。

为了避免这种歧义,可以在方法名中使用\_来人工定义遍历点。所以上面的函数名可以定义成如下形式:

List<Person> findByAddress_ZipCode(ZipCode zipCode);

因为这里将下划线当作保留字符,所以强烈建议按照标准java命名规范命名(不要在属性名中使用下划线,要使用驼峰规范)。

4. 特殊参数操作

在前面的例子中,通过函数参数来操作查询中的参数。除此之外,还有两个特殊类型的参数Pageable和Sort,用于动态实现分页查询和排序。

下例在查询中使用到了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);

第一个方法传递了一个org.springframework.data.domain.Pageable实例实现在定义的查询上动态添加分页。Page知道元素的总数和页数。这个数量是通过触发一个计数查询得到的。但这样作可能比较消耗资源(取决于选用的数据库),作为替代方案可以返回一个Slice。Slice只知道下一个Slice是否可用,一般这样可能就足以满足查询大数据结果集的需求。

排序操作也通过Pageable实例处理。如果只需要排序,添加org.springframework.data.domain.Pageable参数到你的函数。这样会返回一个List。这种情况下,创建Page实例所需的额外元数据并没有被创建(额外的计数查询)。当然,它要求查询限定在给定范围的实体中。

5. 限制查询结果范围

可以通过first或top关键字限制查询结果范围,两者可以交换使用。在top或first后面设置一个可选的数字值用于指定返回结果的数量上限。如果没设置该数量,默认值为1.

下例演示限定返回结果集大小:

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关键字封装结果。

6. 流化查询结果

可以通过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块,如下例:

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

注:不是所有的Spring Data模块支持Stream<T>返回类型

7. 异步查询

Repository查询可以通过Spring’s asynchronous method execution capability异步执行。这表示函数在调用后会立即返回,而实际查询会在Spring 的TaskExecutor作为一个任务执行。异步查询和响应式查询不同,所以不要混淆。

下例演示了几种异步查询:

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

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

@Async
ListenableFuture<User> findOneByLastname(String lastname);    

猜你喜欢

转载自blog.csdn.net/gavinabc/article/details/81774582