SpringData的方法命名规则
最近在学MongoDB的是时候,发现如下:
public interface CommentRepository extends MongoRepository<Comment,String > {
Page<Comment> findByParentid(String parentid, Pageable pageable);
}
这个方法就可以直接用了,也是根据parentid查询的且分页的结果,就感觉特别奇怪,也没有发现xml,方法却能执行。
原来是SpringData的命令规则,之前没有深入学过,于是赶紧理解一下。
方法命名规则查询
顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照SpringData JPA提供的方法。
命名规则定义方法的名称,就可以完成查询工作。
SpringData JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询.
按照SpringData JPA定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析 。
List<Article> findByTitle(String title);
//根据标题模糊查询
List<Article> findByTitleLike(String title);
//根据标题和作者查询
List<Article> findByTitleAndAuthor(String title, String author);
//根据ID范围查询
List<Article> findByAidBetween(Integer starAid, Integer endAid);
List<Article> findByAidLessThan(Integer endAid);
List<Article> findByAidIn(List<Integer> aids);
//根据创建时间之后查询
List<Article> findByCreateTimeAfter(Date createTime);
关键字 | 例子 | 对应的JPQL语句 |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ? 2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs, findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
关键字 | 例子 | 对应的JPQL语句 |
---|---|---|
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = U |
官网说明如下:
4.4. 定义查询方法
存储库代理有两种从方法名称派生特定于 Store 的查询的方式:
- 通过直接从方法名称派生查询。
- 通过使用手动定义的查询。
可用选项取决于实际 Store。但是,必须有一个策略来决定要创建的实际查询。下一节将介绍可用的选项。
4.4.1. 查询查询策略
以下策略可用于存储库基础结构来解决查询。使用 XML 配置,您可以通过query-lookup-strategy
属性在名称空间中配置策略。对于 Java 配置,可以使用Enable${store}Repositories
注解的queryLookupStrategy
属性。某些数据存储可能不支持某些策略。
CREATE
尝试从查询方法名称构造特定于 Store 的查询。通用方法是从方法名称中删除一组给定的众所周知的前缀,然后解析该方法的其余部分。您可以在“ Query Creation”中阅读有关查询构造的更多信息。USE_DECLARED_QUERY
尝试查找已声明的查询,如果找不到则抛出异常。该查询可以通过某处的 Comments 定义,也可以通过其他方式声明。请查阅特定 Store 的文档以找到该 Store 的可用选项。如果存储库基础结构在引导时找不到该方法的声明查询,则它将失败。CREATE_IF_NOT_FOUND
(默认)组合了CREATE
和USE_DECLARED_QUERY
。它首先查找一个声明的查询,如果找不到声明的查询,它将创建一个基于名称的自定义方法查询。这是默认的查找策略,因此,如果未显式配置任何内容,则使用该策略。它允许通过方法名称快速定义查询,还可以通过根据需要引入已声明的查询来自定义调整这些查询。
4.4.2. 查询创建
内置在 Spring Data 存储库基础结构中的查询构建器机制对于在存储库实体上构建约束查询很有用。该机制从方法中剥离前缀find…By
,read…By
,query…By
,count…By
和get…By
,并开始解析其余部分。 Introduction 子句可以包含其他表达式,例如Distinct
,以在要创建的查询上设置不同的标志。但是,第一个By
充当分隔符,以指示实际标准的开始。在最基本的级别上,您可以定义实体属性的条件,并将它们与And
和Or
串联。以下示例显示了如何创建许多查询:
例子 13.从方法名查询创建
public 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
结合使用。您还将获得对属性表达式的支持,例如Between
,LessThan
,GreaterThan
和Like
。支持的运算符可能因数据存储而异,因此请参考参考文档的相应部分。 - 方法解析器支持为单个属性(例如
findByLastnameIgnoreCase(…)
)或支持忽略大小写的类型的所有属性(通常为String
instance_,例如findByLastnameAndFirstnameAllIgnoreCase(…)
)设置IgnoreCase
标志。是否支持忽略大小写可能因 Store 而异,因此请参考参考文档中有关 Store 特定查询方法的相关部分。 - 您可以通过将
OrderBy
子句附加到引用属性的查询方法并提供排序方向(Asc
或Desc
)来应用静态排序。要创建支持动态排序的查询方法,请参阅“ 特殊参数处理”。
4.4.3. 属性表达式
如上例所示,属性表达式只能引用被管实体的直接属性。在查询创建时,您已经确保已解析的属性是托管域类的属性。但是,您也可以通过遍历嵌套属性来定义约束。考虑以下方法签名:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设Person
具有Address
和ZipCode
。在这种情况下,该方法将创建属性遍历x.address.zipCode
。解析算法首先将整个部分(AddressZipCode
)解释为属性,然后在域类中检查具有该名称的属性(未大写)。如果算法成功,它将使用该属性。如果不是,该算法将驼峰案例部分的源从右侧分为头和尾,并尝试找到相应的属性,在我们的示例中为AddressZip
和Code
。如果该算法找到了具有该头部的属性,则将其取为尾部,并 continue 从此处开始构建树,以刚才描述的方式将尾部向上拆分。如果第一个分割不匹配,则算法将分割点移到左侧(Address
,ZipCode
)并 continue。
尽管这在大多数情况下应该可行,但算法可能会选择错误的属性。假设Person
类也具有addressZip
属性。该算法将在第一轮拆分中已经匹配,选择错误的属性,然后失败(因为addressZip
的类型可能没有code
属性)。
要解决这种歧义,您可以在方法名称中使用_
来手动定义遍历点。因此,我们的方法名称如下:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
因为我们将下划线字符视为保留字符,所以我们强烈建议您遵循以下标准 Java 命名约定(即,在属性名称中不使用下划线,而使用驼峰大小写)。
4.4.4. 特殊参数处理
要处理查询中的参数,请定义方法参数,如前面的示例所示。除此之外,基础架构还可以识别某些特定类型(例如Pageable
和Sort
),以将分页和排序动态应用于您的查询。下面的示例演示了这些功能:
例子 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);
第一种方法使您可以将org.springframework.data.domain.Pageable
实例传递给查询方法,以将分页动态添加到静态定义的查询中。 Page
知道可用元素和页面的总数。它是通过基础结构触发计数查询来计算总数来实现的。由于这可能很昂贵(取决于使用的 Store),因此您可以返回Slice
。 Slice
仅知道下一个Slice
是否可用,当遍历较大的结果集时可能就足够了。
排序选项也通过Pageable
实例处理。如果只需要排序,则将org.springframework.data.domain.Sort
参数添加到您的方法中。如您所见,返回List
也是可能的。在这种情况下,不会创建构建实际Page
实例所需的其他元数据(这反过来,这意味着不会发出本来必要的其他计数查询)。而是,它将查询限制为仅查找给定范围的实体。
要查明整个查询可获得多少页,您必须触发另一个计数查询。默认情况下,此查询源自您实际触发的查询。
4.4.5. 限制查询结果
可以使用first
或top
关键字来限制查询方法的结果,这些关键字可以互换使用。可以将一个可选的数值附加到top
或first
以指定要返回的最大结果大小。如果省略该数字,则假定结果大小为 1.以下示例显示了如何限制查询大小:
例子 15.用Top
和First
限制查询的结果大小
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
参数来限制结果与动态排序的组合,可以让您表达对最小的“ K”元素和对“ K”的最大元素的查询方法。
4.4.6. 流查询结果
通过使用 Java 8 Stream<T>
作为返回类型,可以递增地处理查询方法的结果。而不是将查询结果包装在Stream
数据存储区中,而是使用特定于方法的方法来执行流传输,如以下示例所示:
例子 16.用 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);
Stream
可能包装了特定于底层数据存储的资源,因此在使用后必须将其关闭。您可以使用close()
方法或使用 Java 7 try-with-resources
块来手动关闭Stream
,如以下示例所示:
例子 17.使用Stream<T>
导致 try-with-resources 块
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
目前,并非所有的 Spring Data 模块都支持Stream<T>
作为返回类型。
4.4.7. 异步查询结果
可以使用Spring 的异步方法执行能力异步运行存储库查询。这意味着该方法在调用时立即返回,而实际查询执行发生在已提交给 Spring TaskExecutor
的任务中。异步查询执行与反应式查询执行不同,因此不应混为一谈。有关响应式支持的更多详细信息,请参阅 Store 特定的文档。以下示例显示了许多异步查询:
@Async
Future<User> findByFirstname(String firstname); (1)
@Async
CompletableFuture<User> findOneByFirstname(String firstname); (2)
@Async
ListenableFuture<User> findOneByLastname(String lastname); (3)
- (1) 使用
java.util.concurrent.Future
作为返回类型。 - (2) 使用 Java 8
java.util.concurrent.CompletableFuture
作为返回类型。 - (3) 使用
org.springframework.util.concurrent.ListenableFuture
作为返回类型。