-
支持的查询方法名称约定和自定义查询方法
-
范围查询的类型安全限制
-
命名参数和位置参数
-
分页和排序
-
投影和查询结果转换
-
自定义实现
Defining Query Methods
-
直接从方法名派生查询
-
使用手动定义的查询
策略的选择取决于实际存储器。存储库的基础结构提供了策略选项,并允许你配置要使用的策略。查询构建器允许你在实体上创建约束查询。它支持嵌套属性遍历、条件连接、投影和限制,从而为你的查询提供灵活性。此外,基础结构还提供分页、排序和限制选项,以便于管理大型数据集。
存储库代理有两种方法可以从方法名称派生存储器特定的查询:
The repository proxy has two ways to derive a store-specific query from the method name:
-
By deriving the query from the method name directly.
-
By using a manually defined query.
可用的选项取决于实际存储器。但是,必须有一个策略来决定创建哪个实际查询。下一节介绍可用的选项。
Available options depend on the actual store. However, there must be a strategy that decides what actual query is created. The next section describes the available options.
Query Lookup Strategies
以下策略可用于存储库基础结构来解析查询。使用 XML 配置时,你可以通过 query-lookup-strategy
属性在命名空间中配置该策略。对于 Java 配置,可以使用 Enable{store}Repositories
注释的 queryLookupStrategy
属性。某些策略可能不受特定数据存储支持。
The following strategies are available for the repository infrastructure to resolve the query.
With XML configuration, you can configure the strategy at the namespace through the query-lookup-strategy
attribute.
For Java configuration, you can use the queryLookupStrategy
attribute of the Enable{store}Repositories
annotation.
Some strategies may not be supported for particular datastores.
-
CREATE
attempts to construct a store-specific query from the query method name. The general approach is to remove a given set of well known prefixes from the method name and parse the rest of the method. You can read more about query construction in “Query Creation”. -
USE_DECLARED_QUERY
tries to find a declared query and throws an exception if it cannot find one. The query can be defined by an annotation somewhere or declared by other means. See the documentation of the specific store to find available options for that store. If the repository infrastructure does not find a declared query for the method at bootstrap time, it fails. -
CREATE_IF_NOT_FOUND
(the default) combinesCREATE
andUSE_DECLARED_QUERY
. It looks up a declared query first, and, if no declared query is found, it creates a custom method name-based query. This is the default lookup strategy and, thus, is used if you do not configure anything explicitly. It allows quick query definition by method names but also custom-tuning of these queries by introducing declared queries as needed.
Query Creation
Spring Data 存储库基础设施中内置的查询构建器机制可用于构建对存储库实体的约束查询。
The query builder mechanism built into the Spring Data repository infrastructure is useful for building constraining queries over entities of the repository.
以下示例展示了如何创建多个查询:
The following example shows how to create a number of queries:
interface PersonRepository extends Repository<Person, 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);
}
解析查询方法名称分为主题和谓词。第一部分(find…By
、exists…By
)定义查询的主题,第二部分形成谓词。引言子句(主题)可以包含更多表达式。find
(或其他引言关键字)和 By
之间的所有文本都视为描述性文本,除非使用某个结果限制关键字,例如 Distinct
为要创建的查询设置一个不同标志或 <<`Top`/First
限制查询结果,repositories.limit-query-result>>。
Parsing query method names is divided into subject and predicate.
The first part (find…By
, exists…By
) defines the subject of the query, the second part forms the predicate.
The introducing clause (subject) can contain further expressions.
Any text between find
(or other introducing keywords) and By
is considered to be descriptive unless using one of the result-limiting keywords such as a Distinct
to set a distinct flag on the query to be created or <<`Top`/First
to limit query results,repositories.limit-query-result>>.
附录包含 full list of query method subject keywords和 query method predicate keywords including sorting and letter-casing modifiers。但是,第一个 `By`用作分隔符来指示实际条件谓词的开始。在非常基本的层面上,您可以在实体属性上定义条件,并使用 `And`和 `Or`连接它们。
The appendix contains the full list of query method subject keywords and query method predicate keywords including sorting and letter-casing modifiers.
However, the first By
acts as a delimiter to indicate the start of the actual criteria predicate.
At a very basic level, you can define conditions on entity properties and concatenate them with And
and Or
.
解析方法的实际结果取决于你为其创建查询的持久性存储器。但是,有一些值得注意的一般事项:
The actual result of parsing the method depends on the persistence store for which you create the query. However, there are some general things to notice:
-
The expressions are usually property traversals combined with operators that can be concatenated. You can combine property expressions with
AND
andOR
. You also get support for operators such asBetween
,LessThan
,GreaterThan
, andLike
for the property expressions. The supported operators can vary by datastore, so consult the appropriate part of your reference documentation. -
The method parser supports setting an
IgnoreCase
flag for individual properties (for example,findByLastnameIgnoreCase(…)
) or for all properties of a type that supports ignoring case (usuallyString
instances — for example,findByLastnameAndFirstnameAllIgnoreCase(…)
). Whether ignoring cases is supported may vary by store, so consult the relevant sections in the reference documentation for the store-specific query method. -
You can apply static ordering by appending an
OrderBy
clause to the query method that references a property and by providing a sorting direction (Asc
orDesc
). To create a query method that supports dynamic sorting, see “Paging, Iterating Large Results, Sorting & Limiting”.
Property Expressions
属性表达式只能引用受管理实体的直接属性,如前面的示例中所示。在创建查询时,你已经确保解析的属性是受管理领域类的属性。但是,你也可以通过遍历嵌套属性来定义约束。请考虑以下方法签名:
Property expressions can refer only to a direct property of the managed entity, as shown in the preceding example. At query creation time, you already make sure that the parsed property is a property of the managed domain class. However, you can also define constraints by traversing nested properties. Consider the following method signature:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设 Person
有一个包含 ZipCode
的 Address
。在这种情况下,该方法创建 x.address.zipCode
属性遍历。解析算法首先将整个部分(AddressZipCode
)解释为属性,并检查领域类是否具有具有该名称的属性(小写)。如果算法成功,它将使用该属性。如果没有,该算法会从右侧将源按骆驼大小写部分拆分为头部和尾部,并尝试找到相应的属性,在我们的示例中,分别是 AddressZip
和 Code
。如果算法找到了具有该头部的属性,它将取尾部并继续从那里构建树,以刚才描述的方式对尾部进行拆分。如果第一次拆分不匹配,该算法会将拆分点向左移动(Address
、ZipCode
)并继续。
Assume a Person
has an Address
with a ZipCode
.
In that case, the method creates the x.address.zipCode
property traversal.
The resolution algorithm starts by interpreting the entire part (AddressZipCode
) as the property and checks the domain class for a property with that name (uncapitalized).
If the algorithm succeeds, it uses that property.
If not, the algorithm splits up the source at the camel-case parts from the right side into a head and a tail and tries to find the corresponding property — in our example, AddressZip
and Code
.
If the algorithm finds a property with that head, it takes the tail and continues building the tree down from there, splitting the tail up in the way just described.
If the first split does not match, the algorithm moves the split point to the left (Address
, ZipCode
) and continues.
虽然这在大多数情况下应该有效,但算法有可能选择错误的属性。假设 Person
类也具有 addressZip
属性。该算法将在第一轮拆分中匹配,选择错误的属性并失败(因为 addressZip
的类型可能没有 code
属性)。
Although this should work for most cases, it is possible for the algorithm to select the wrong property.
Suppose the Person
class has an addressZip
property as well.
The algorithm would match in the first split round already, choose the wrong property, and fail (as the type of addressZip
probably has no code
property).
要解决此歧义,你可以在方法名中使用 _
手动定义遍历点。因此,我们的方法名称如下:
To resolve this ambiguity you can use _
inside your method name to manually define traversal points.
So our method name would be as follows:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
因为我们把下划线( Because we treat underscores ( |
字段名可以以下划线开头,比如 String name
。务必保留 ,比如在
name
中保留,并使用双 分隔嵌套路径,比如
user__name
。
Field names may start with underscores like String name
.
Make sure to preserve the as in
name
and use double to split nested paths like
user__name
.
.Upper Case Field Names:
完全大写的字段名可以按原样使用。如果适用,嵌套路径需要通过 _
分隔,比如在 USER_name
中。
Field names that are all uppercase can be used as such.
Nested paths if applicable require splitting via _
as in USER_name
.
.Field Names with 2nd uppercase letter:
字段名由一个起始小写字母后跟一个大写字母组成,比如 String qCode
,可以通过以两个大写字母开头来解析,比如 QCode
。请注意潜在路径歧义。
Field names that consist of a starting lower case letter followed by an uppercase one like String qCode
can be resolved by starting with two upper case letters as in QCode
.
Please be aware of potential path ambiguities.
.Path Ambiguities:
在以下示例中,属性 qCode
和 q
的排列,以及包含名为 code
的属性的 q
,会为路径 QCode
创建歧义。
In the following sample the arrangement of properties qCode
and q
, with q
containing a property called code
, creates an ambiguity for the path QCode
.
record Container(String qCode, Code q) {}
record Code(String code) {}
由于最先考虑直接匹配属性,因此任何潜在嵌套路径都不会被考虑,并且该算法会选择 qCode
字段。为了在 q
中选择 code
字段,需要下划线表示法 Q_Code
。
Since a direct match on a property is considered first, any potential nested paths will not be considered and the algorithm picks the qCode
field.
In order to select the code
field in q
the underscore notation Q_Code
is required.
Repository Methods Returning Collections or Iterables
返回多个结果的查询方法可以使用标准 Java Iterable
、List`和`Set
。除此之外,我们还支持返回 Spring Data 的 Streamable
,这是 `Iterable`的自定义扩展,以及由 Vavr提供的集合类型。请参阅解释所有可能的 query method return types的附录。
Query methods that return multiple results can use standard Java Iterable
, List
, and Set
.
Beyond that, we support returning Spring Data’s Streamable
, a custom extension of Iterable
, as well as collection types provided by Vavr.
Refer to the appendix explaining all possible query method return types.
Using Streamable as Query Method Return Type
你可以使用 Streamable
作为 Iterable
或任何集合类型的替代。它提供便利方法来访问非并行 Stream
(Iterable
中没有)以及直接 ….filter(…)
和 ….map(…)
元素并连接到其他 Streamable
的能力:
You can use Streamable
as alternative to Iterable
or any collection type.
It provides convenience methods to access a non-parallel Stream
(missing from Iterable
) and the ability to directly ….filter(…)
and ….map(…)
over the elements and concatenate the Streamable
to others:
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"));
Returning Custom Streamable Wrapper Types
为集合提供专用的包装器类型是一个常用模式,用于为返回多个元素的查询结果提供 API。通常,通过调用返回类似集合类型的存储库方法并手动创建包装器类型实例来使用这些类型。你可以避免此额外的步骤,因为 Spring Data 允许你在它们满足以下条件时将这些包装器类型用作查询方法返回类型:
Providing dedicated wrapper types for collections is a commonly used pattern to provide an API for a query result that returns multiple elements. Usually, these types are used by invoking a repository method returning a collection-like type and creating an instance of the wrapper type manually. You can avoid that additional step as Spring Data lets you use these wrapper types as query method return types if they meet the following criteria:
-
The type implements
Streamable
. -
The type exposes either a constructor or a static factory method named
of(…)
orvalueOf(…)
that takesStreamable
as an argument.
下面的清单显示了一个示例:
The following listing shows an example:
class Product { 1
MonetaryAmount getPrice() { … }
}
@RequiredArgsConstructor(staticName = "of")
class Products implements Streamable<Product> { 2
private final Streamable<Product> streamable;
public MonetaryAmount getTotal() { 3
return streamable.stream()
.map(Priced::getPrice)
.reduce(Money.of(0), MonetaryAmount::add);
}
@Override
public Iterator<Product> iterator() { 4
return streamable.iterator();
}
}
interface ProductRepository implements Repository<Product, Long> {
Products findAllByDescriptionContaining(String text); 5
}
1 | A Product entity that exposes API to access the product’s price. |
2 | A wrapper type for a Streamable<Product> that can be constructed by using Products.of(…) (factory method created with the Lombok annotation).
A standard constructor taking the Streamable<Product> will do as well. |
3 | The wrapper type exposes an additional API, calculating new values on the Streamable<Product> . |
4 | Implement the Streamable interface and delegate to the actual result. |
5 | That wrapper type Products can be used directly as a query method return type.
You do not need to return Streamable<Product> and manually wrap it after the query in the repository client. |
Support for Vavr Collections
Vavr 是一个在 Java 中包含函数式编程概念的库。它提供了可作为查询方法返回类型的自定义集合类型,如下表所示:
Vavr is a library that embraces functional programming concepts in Java. It ships with a custom set of collection types that you can use as query method return types, as the following table shows:
Vavr collection type | Used Vavr implementation type | Valid Java source types |
---|---|---|
|
|
|
|
|
|
|
|
|
你可以使用第一列中的类型(或其子类型)作为查询方法返回类型,并根据实际查询结果的 Java 类型(第三列)获取第二列中用作实现类型的类型。或者,你可以声明 Traversable
(Vavr Iterable
等效项),然后我们从实际返回值派生实现类。也就是说,java.util.List
会变成 Vavr List
或 Seq
,java.util.Set
会变成 Vavr LinkedHashSet
Set
,依此类推。
You can use the types in the first column (or subtypes thereof) as query method return types and get the types in the second column used as implementation type, depending on the Java type of the actual query result (third column).
Alternatively, you can declare Traversable
(the Vavr Iterable
equivalent), and we then derive the implementation class from the actual return value.
That is, a java.util.List
is turned into a Vavr List
or Seq
, a java.util.Set
becomes a Vavr LinkedHashSet
Set
, and so on.
Streaming Query Results
你可以通过使用 Java 8 Stream<T>
作为返回类型来增量处理查询方法的结果。数据存储器特定方法用于执行流式处理,而不是将查询结果包装在 Stream
中,如以下示例所示:
You can process the results of query methods incrementally by using a Java 8 Stream<T>
as the return type.
Instead of wrapping the query results in a Stream
, data store-specific methods are used to perform the streaming, as shown in the following example:
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);
A |
A |
Stream<T>
result in a try-with-resources
blocktry (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
目前并非所有 Spring Data 模块都支持 |
Not all Spring Data modules currently support |
Asynchronous Query Results
您可以使用 {spring-framework-docs}/integration/scheduling.html[Spring 的异步方法运行功能] 异步运行存储库查询。这意味着该方法在调用时会立即返回,而实际查询发生在已提交给 Spring TaskExecutor
的任务中。异步查询不同于响应式查询,不应该混合使用。请参阅特定于存储的文档,以了解更多有关响应式支持的详细信息。以下示例显示了一些异步查询:
You can run repository queries asynchronously by using {spring-framework-docs}/integration/scheduling.html[Spring’s asynchronous method running capability].
This means the method returns immediately upon invocation while the actual query occurs in a task that has been submitted to a Spring TaskExecutor
.
Asynchronous queries differ from reactive queries and should not be mixed.
See the store-specific documentation for more details on reactive support.
The following example shows a number of asynchronous queries:
@Async
Future<User> findByFirstname(String firstname); 1
@Async
CompletableFuture<User> findOneByFirstname(String firstname); 2
1 | Use java.util.concurrent.Future as the return type. |
2 | Use a Java 8 java.util.concurrent.CompletableFuture as the return type. |
Paging, Iterating Large Results, Sorting & Limiting
要在查询中处理参数,请将方法参数定义为前面示例中已经看到的那样。除此之外,基础设施识别某些特定类型(如 Pageable
、Sort
和 Limit
),以动态地对你的查询应用分页、排序和限制。以下示例演示了这些功能:
To handle parameters in your query, define method parameters as already seen in the preceding examples.
Besides that, the infrastructure recognizes certain specific types like Pageable
, Sort
and Limit
, to apply pagination, sorting and limiting to your queries dynamically.
The following example demonstrates these features:
Pageable
, Slice
, Sort
and Limit
in query methodsPage<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, Sort sort, Limit limit);
List<User> findByLastname(String lastname, Pageable pageable);
需要 Sort
、Pageable
和 Limit
的 API 预计非 null
值将传递到方法中。如果您不想应用任何排序或分页,请使用 Sort.unsorted()
、Pageable.unpaged()
和 Limit.unlimited()
。
APIs taking Sort
, Pageable
and Limit
expect non-null
values to be handed into methods.
If you do not want to apply any sorting or pagination, use Sort.unsorted()
, Pageable.unpaged()
and Limit.unlimited()
.
第一个方法允许你将 org.springframework.data.domain.Pageable
实例传递给查询方法,以动态地向你的静态定义查询添加分页。Page
了解可用元素和页面的总数。它通过基础设施触发计数查询来计算总数。由于这可能很耗费资源(取决于所使用的存储),你也可以返回 Slice
。Slice
只知道是否有下一个 Slice
可用,这在遍历较大的结果集时可能就足够了。
The first method lets you pass an org.springframework.data.domain.Pageable
instance to the query method to dynamically add paging to your statically defined query.
A Page
knows about the total number of elements and pages available.
It does so by the infrastructure triggering a count query to calculate the overall number.
As this might be expensive (depending on the store used), you can instead return a Slice
.
A Slice
knows only about whether a next Slice
is available, which might be sufficient when walking through a larger result set.
排序选项也通过 Pageable
实例进行处理。如果你只需要排序,请向你的方法添加 org.springframework.data.domain.Sort
参数。如你所见,也可以返回 List
。在这种情况下,不会创建构建实际 Page
实例所需的附加元数据(这反过来意味着不会发出必需的附加计数查询)。相反,它会将查询限制为仅查找给定范围内的实体。
Sorting options are handled through the Pageable
instance, too.
If you need only sorting, add an org.springframework.data.domain.Sort
parameter to your method.
As you can see, returning a List
is also possible.
In this case, the additional metadata required to build the actual Page
instance is not created (which, in turn, means that the additional count query that would have been necessary is not issued).
Rather, it restricts the query to look up only the given range of entities.
要找出整个查询获得多少页,您必须触发附加的计数查询。默认情况下,此查询是从您实际触发的查询派生出来的。 |
To find out how many pages you get for an entire query, you have to trigger an additional count query. By default, this query is derived from the query you actually trigger. |
只能在查询方法中一次使用特殊参数。上面描述的一些特殊参数是互斥的。请考虑以下无效参数组合列表。
Special parameters may only be used once within a query method. Some special parameters described above are mutually exclusive. Please consider the following list of invalid parameter combinations.
Parameters | Example | Reason |
---|---|---|
|
|
|
|
|
|
用于限制结果的 Top
关键字可以与 Pageable
一起使用,而 Top
定义了结果的总数最大值,而 Pageable 参数可能会减少此数。
The Top
keyword used to limit results can be used to along with Pageable
whereas Top
defines the total maximum of results, whereas the Pageable parameter may reduce this number.
Which Method is Appropriate?
Spring Data 抽象提供的价值也许最能通过下表中概述的可能的查询方法返回类型来体现。该表显示了你可以从查询方法返回哪些类型
The value provided by the Spring Data abstractions is perhaps best shown by the possible query method return types outlined in the following table below. The table shows which types you can return from a query method
Method | Amount of Data Fetched | Query Structure | Constraints |
---|---|---|---|
<<`List<T>`,repositories.collections-and-iterables>> |
All results. |
Single query. |
Query results can exhaust all memory. Fetching all data can be time-intensive. |
<<`Streamable<T>`,repositories.collections-and-iterables.streamable>> |
All results. |
Single query. |
Query results can exhaust all memory. Fetching all data can be time-intensive. |
<<`Stream<T>`,repositories.query-streaming>> |
Chunked (one-by-one or in batches) depending on |
Single query using typically cursors. |
Streams must be closed after usage to avoid resource leaks. |
|
Chunked (one-by-one or in batches) depending on |
Single query using typically cursors. |
Store module must provide reactive infrastructure. |
|
|
One to many queries fetching data starting at |
A * * |
|
|
One to many queries starting at |
Often times, * Offset-based queries becomes inefficient when the offset is too large because the database still has to materialize the full result. |
Paging and Sorting
您可以使用属性名称来定义简单的排序表达式。您可以链接表达式将多个条件收集到一个表达式。
You can define simple sorting expressions by using property names. You can concatenate expressions to collect multiple criteria into one expression.
Sort sort = Sort.by("firstname").ascending()
.and(Sort.by("lastname").descending());
对于定义排序表达式的更安全的方法,从要为其定义排序表达式的类型开始,并使用方法引用来定义排序的属性。
For a more type-safe way to define sort expressions, start with the type for which to define the sort expression and use method references to define the properties on which to sort.
TypedSort<Person> person = Sort.sort(Person.class);
Sort sort = person.by(Person::getFirstname).ascending()
.and(person.by(Person::getLastname).descending());
|
|
如果您的存储实现支持 Querydsl,您还可以使用生成的元模型类型来定义排序表达式:
If your store implementation supports Querydsl, you can also use the generated metamodel types to define sort expressions:
QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));
Limiting Query Results
除了分页外,可以使用专用的 Limit
参数来限制结果大小。您还可通过使用 First
或 Top
关键字来限制查询方法的结果,您可以交替使用这两个关键字,但不能与 Limit
参数混合使用。可以在 Top
或 First
后附加一个可选的数字值来指定要返回的最大结果大小。如果没有数字,则假定结果大小为 1。以下示例演示如何限制查询大小:
In addition to paging it is possible to limit the result size using a dedicated Limit
parameter.
You can also limit the results of query methods by using the First
or Top
keywords, which you can use interchangeably but may not be mixed with a Limit
parameter.
You can append an optional numeric value to Top
or First
to specify the maximum result size to be returned.
If the number is left out, a result size of 1 is assumed.
The following example shows how to limit the query size:
Top
and First
List<User> findByLastname(Limit limit);
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
关键字包装结果。
The limiting expressions also support the Distinct
keyword for datastores that support distinct queries.
Also, for the queries that limit the result set to one instance, wrapping the result into with the Optional
keyword is supported.
如果将分页或切片应用于限制查询分页(以及可用页数的计算),则它将应用于有限结果中。
If pagination or slicing is applied to a limiting query pagination (and the calculation of the number of available pages), it is applied within the limited result.
对结果进行限制,同时使用 |
Limiting the results in combination with dynamic sorting by using a |