MongoDB-specific Query Methods
您通常在存储库上触发的多数数据访问操作都会导致对 MongoDB 数据库执行查询。定义此类查询是声明存储库接口上方法的问题,如下例所示:
Most of the data access operations you usually trigger on a repository result in a query being executed against the MongoDB databases. Defining such a query is a matter of declaring a method on the repository interface, as the following example shows:
- Imperative
-
public interface PersonRepository extends PagingAndSortingRepository<Person, String> { List<Person> findByLastname(String lastname); 1 Page<Person> findByFirstname(String firstname, Pageable pageable); 2 Person findByShippingAddresses(Address address); 3 Person findFirstByLastname(String lastname); 4 Stream<Person> findAllBy(); 5 }
1 | The findByLastname method shows a query for all people with the given last name.
The query is derived by parsing the method name for constraints that can be concatenated with And and Or .
Thus, the method name results in a query expression of {"lastname" : lastname} . |
2 | Applies pagination to a query.
You can equip your method signature with a Pageable parameter and let the method return a Page instance and Spring Data automatically pages the query accordingly. |
3 | Shows that you can query based on properties that are not primitive types.
Throws IncorrectResultSizeDataAccessException if more than one match is found. |
4 | Uses the First keyword to restrict the query to only the first result.
Unlike <3>, this method does not throw an exception if more than one match is found. |
5 | Uses a Java 8 Stream that reads and converts individual elements while iterating the stream.
|
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {
Flux<Person> findByFirstname(String firstname); 1
Flux<Person> findByFirstname(Publisher<String> firstname); 2
Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); 3
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname); 4
Mono<Person> findFirstByLastname(String lastname); 5
}
1 | The method shows a query for all people with the given lastname . The query is derived by parsing the method name for constraints that can be concatenated with And and Or . Thus, the method name results in a query expression of {"lastname" : lastname} . |
2 | The method shows a query for all people with the given firstname once the firstname is emitted by the given Publisher . |
3 | Use Pageable to pass offset and sorting parameters to the database. |
4 | Find a single entity for the given criteria. It completes with IncorrectResultSizeDataAccessException on non-unique results. |
5 | Unless <4>, the first entity is always emitted even if the query yields more result documents. |
响应式存储库不支持 Page
返回类型(如 Mono<Page>
所示)。
The Page
return type (as in Mono<Page>
) is not supported by reactive repositories.
可以在派生查找器方法中使用 Pageable
,以将 sort
、limit
和 offset
参数传递给查询以减少负载和网络流量。返回的 Flux
将仅在声明的范围内发出数据。
It is possible to use Pageable
in derived finder methods, to pass on sort
, limit
and offset
parameters to the query to reduce load and network traffic.
The returned Flux
will only emit data within the declared range.
Pageable page = PageRequest.of(1, 10, Sort.by("lastname"));
Flux<Person> persons = repository.findByFirstnameOrderByLastname("luke", page);
我们不支持引用在域类中映射为 |
We do not support referring to parameters that are mapped as |
Keyword | Sample | Logical result |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
如果属性条件比较一个文档,则文档中字段的顺序和精确相等很重要。 |
If the property criterion compares a document, the order of the fields and exact equality in the document matters. |
Geo-spatial Queries
正如您在之前的关键词表中看到的,几个关键词在 MongoDB 查询中触发了地理空间操作。Near
关键词允许进一步的修改,如下面几个示例所示。
As you saw in the preceding table of keywords, a few keywords trigger geo-spatial operations within a MongoDB query.
The Near
keyword allows some further modification, as the next few examples show.
以下示例展示了如何定义一个 near
查询,用它根据给定点查到所有给定距离内的人员:
The following example shows how to define a near
query that finds all persons with a given distance of a given point:
Near
queries-
Imperative
-
Reactive
public interface PersonRepository extends MongoRepository<Person, String> {
// { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
List<Person> findByLocationNear(Point location, Distance distance);
}
interface PersonRepository extends ReactiveMongoRepository<Person, String> {
// { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
Flux<Person> findByLocationNear(Point location, Distance distance);
}
向查询方法中增加一个 Distance
参数,可以将结果限制为给定距离内的内容。如果 Distance
中包含一个 Metric
,我们将直接使用 $nearSphere
,而不是 $code
,如下示例所示:
Adding a Distance
parameter to the query method allows restricting results to those within the given distance.
If the Distance
was set up containing a Metric
, we transparently use $nearSphere
instead of $code
, as the following example shows:
Distance
with Metrics
Point point = new Point(43.7, 48.8);
Distance distance = new Distance(200, Metrics.KILOMETERS);
… = repository.findByLocationNear(point, distance);
// {'location' : {'$nearSphere' : [43.7, 48.8], '$maxDistance' : 0.03135711885774796}}
响应式地理空间存储库查询支持域类型和响应式包装器类型中的 |
Reactive Geo-spatial repository queries support the domain type and |
使用 Metric
的 Distance
,将导致添加一个 $nearSphere
(而不是常规的 $near
)子句。除此之外,实际的距离将根据使用的 Metric
来计算。
Using a Distance
with a Metric
causes a $nearSphere
(instead of a plain $near
) clause to be added.
Beyond that, the actual distance gets calculated according to the Metrics
used.
(注意,Metric
并不涉及测量单位。它可以是英里,而不是公里。metric
更合适地指的是度量系统的概念,无论您使用哪个系统。)
(Note that Metric
does not refer to metric units of measure.
It could be miles rather than kilometers.
Rather, metric
refers to the concept of a system of measurement, regardless of which system you use.)
在目标属性上使用 |
Using |
-
Imperative
-
Reactive
public interface PersonRepository extends MongoRepository<Person, String> {
// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults<Person> findByLocationNear(Point location);
// No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
// 'distanceMultiplier' : metric.multiplier, 'spherical' : true }
GeoResults<Person> findByLocationNear(Point location, Distance distance);
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
// 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
// 'spherical' : true }
GeoResults<Person> findByLocationNear(Point location, Distance min, Distance max);
// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults<Person> findByLocationNear(Point location);
}
interface PersonRepository extends ReactiveMongoRepository<Person, String> {
// {'geoNear' : 'location', 'near' : [x, y] }
Flux<GeoResult<Person>> findByLocationNear(Point location);
// No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
// 'distanceMultiplier' : metric.multiplier, 'spherical' : true }
Flux<GeoResult<Person>> findByLocationNear(Point location, Distance distance);
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
// 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
// 'spherical' : true }
Flux<GeoResult<Person>> findByLocationNear(Point location, Distance min, Distance max);
// {'geoNear' : 'location', 'near' : [x, y] }
Flux<GeoResult<Person>> findByLocationNear(Point location);
}
JSON-based Query Methods and Field Restriction
通过向您的仓库查询方法中添加 org.springframework.data.mongodb.repository.Query
注解,您可以指定一个 MongoDB JSON 查询字符串来使用,而不是从方法名称中派生查询,如下示例所示:
By adding the org.springframework.data.mongodb.repository.Query
annotation to your repository query methods, you can specify a MongoDB JSON query string to use instead of having the query be derived from the method name, as the following example shows:
-
Imperative
-
Reactive
public interface PersonRepository extends MongoRepository<Person, String> {
@Query("{ 'firstname' : ?0 }")
List<Person> findByThePersonsFirstname(String firstname);
}
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query("{ 'firstname' : ?0 }")
Flux<Person> findByThePersonsFirstname(String firstname);
}
?0
占位符允许您将方法参数中的值代入 JSON 查询字符串。
The ?0
placeholder lets you substitute the value from the method arguments into the JSON query string.
|
|
您还可以使用 filter
属性来限制映射到 Java 对象的属性集,如下示例所示:
You can also use the filter property to restrict the set of properties that is mapped into the Java object, as the following example shows:
-
Imperative
-
Reactive
public interface PersonRepository extends MongoRepository<Person, String> {
@Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
List<Person> findByThePersonsFirstname(String firstname);
}
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
Flux<Person> findByThePersonsFirstname(String firstname);
}
上一示例中的查询仅返回 Person
对象的 firstname
、lastname
和 Id
属性。age
属性(java.lang.Integer
)未设置,因此其值为 null。
The query in the preceding example returns only the firstname
, lastname
and Id
properties of the Person
objects.
The age
property, a java.lang.Integer
, is not set and its value is therefore null.
JSON-based Queries with SpEL Expressions
查询字符串和字段定义可以使用 SpEL 表达式结合,在运行时创建动态查询。SpEL 表达式可以提供谓词值,并可用于使用子文档扩展谓词。
Query strings and field definitions can be used together with SpEL expressions to create dynamic queries at runtime. SpEL expressions can provide predicate values and can be used to extend predicates with subdocuments.
表达式通过包含所有参数的数组来公开方法参数。以下查询使用 [0]
来声明 lastname
的谓词值(相当于 ?0
参数绑定):
Expressions expose method arguments through an array that contains all the arguments.
The following query uses [0]
to declare the predicate value for lastname
(which is equivalent to the ?0
parameter binding):
-
Imperative
-
Reactive
public interface PersonRepository extends MongoRepository<Person, String> {
@Query("{'lastname': ?#{[0]} }")
List<Person> findByQueryWithExpression(String param0);
}
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query("{'lastname': ?#{[0]} }")
Flux<Person> findByQueryWithExpression(String param0);
}
表达式可用于调用函数、评估条件和构造值。与 JSON 结合使用的 SpEL 表达式会显现出副作用,因为 SpEL 中类似 Map 的声明读取起来像 JSON,如下示例所示:
Expressions can be used to invoke functions, evaluate conditionals, and construct values. SpEL expressions used in conjunction with JSON reveal a side-effect, because Map-like declarations inside of SpEL read like JSON, as the following example shows:
-
Imperative
-
Reactive
public interface PersonRepository extends MongoRepository<Person, String> {
@Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
List<Person> findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
}
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
Flux<Person> findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
}
查询字符串中的 SpEL 可能是一种增强查询的有力方法。但是,它们还可能接受广泛的无用参数。确保在将字符串传递到查询之前进行清理,以避免创建漏洞或对查询进行不需要的更改。
SpEL in query strings can be a powerful way to enhance queries. However, they can also accept a broad range of unwanted arguments. Make sure to sanitize strings before passing them to the query to avoid creation of vulnerabilities or unwanted changes to your query.
表达式支持可以通过查询 SPI 来扩展:EvaluationContextExtension
和 ReactiveEvaluationContextExtension
。查询 SPI 可以添加属性和函数,并自定义根对象。扩展从应用程序上下文中检索,在生成查询时对 SpEL 进行评估。以下示例展示了如何使用评估上下文扩展:
Expression support is extensible through the Query SPI: EvaluationContextExtension
& ReactiveEvaluationContextExtension
The Query SPI can contribute properties and functions and can customize the root object.
Extensions are retrieved from the application context at the time of SpEL evaluation when the query is built.
The following example shows how to use an evaluation context extension:
-
Imperative
-
Reactive
public class SampleEvaluationContextExtension extends EvaluationContextExtensionSupport {
@Override
public String getExtensionId() {
return "security";
}
@Override
public Map<String, Object> getProperties() {
return Collections.singletonMap("principal", SecurityContextHolder.getCurrent().getPrincipal());
}
}
public class SampleEvaluationContextExtension implements ReactiveEvaluationContextExtension {
@Override
public String getExtensionId() {
return "security";
}
@Override
public Mono<? extends EvaluationContextExtension> getExtension() {
return Mono.just(new EvaluationContextExtensionSupport() { ... });
}
}
自己引导 |
Bootstrapping |
响应式查询方法可以使用 |
Reactive query methods can make use of |
Full-text Search Queries
MongoDB 的全文搜索功能是特定于存储的,因此可以在 `MongoRepository`上找到,而不是在更为通用的 `CrudRepository`上。我们需要一个带有全文索引的文档(请参阅 "`Text Indexes`"以了解如何创建全文索引)。
MongoDB’s full-text search feature is store-specific and, therefore, can be found on MongoRepository
rather than on the more general CrudRepository
.
We need a document with a full-text index (see “Text Indexes” to learn how to create a full-text index).
MongoRepository
上其他方法将 TextCriteria
作为输入参数。除了这些显式方法外,还可以添加一个从 TextCriteria
衍生的存储库方法。这些条件作为其他 AND
条件添加。一旦实体包含一个带有 @TextScore
注解的属性,就可以检索文档的全文分数。此外,@TextScore
注解还允许按文档分数排序,如下示例所示:
Additional methods on MongoRepository
take TextCriteria
as an input parameter.
In addition to those explicit methods, it is also possible to add a TextCriteria
-derived repository method.
The criteria are added as an additional AND
criteria.
Once the entity contains a @TextScore
-annotated property, the document’s full-text score can be retrieved.
Furthermore, the @TextScore
annotated also makes it possible to sort by the document’s score, as the following example shows:
@Document
class FullTextDocument {
@Id String id;
@TextIndexed String title;
@TextIndexed String content;
@TextScore Float score;
}
interface FullTextRepository extends Repository<FullTextDocument, String> {
// Execute a full-text search and define sorting dynamically
List<FullTextDocument> findAllBy(TextCriteria criteria, Sort sort);
// Paginate over a full-text search result
Page<FullTextDocument> findAllBy(TextCriteria criteria, Pageable pageable);
// Combine a derived query with a full-text search
List<FullTextDocument> findByTitleOrderByScoreDesc(String title, TextCriteria criteria);
}
Sort sort = Sort.by("score");
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny("spring", "data");
List<FullTextDocument> result = repository.findAllBy(criteria, sort);
criteria = TextCriteria.forDefaultLanguage().matching("film");
Page<FullTextDocument> page = repository.findAllBy(criteria, PageRequest.of(1, 1, sort));
List<FullTextDocument> result = repository.findByTitleOrderByScoreDesc("mongodb", criteria);
Aggregation Methods
信息库层提供了通过带注释的信息库查询方法与 the aggregation framework交互的方法。与 JSON based queries类似,你可以使用 org.springframework.data.mongodb.repository.Aggregation`注释定义管道。该定义可能包含简单占位符(如 `?0
),也可能包含 SpEL expressions ?#{ … }
。
The repository layer offers means to interact with the aggregation framework via annotated repository query methods.
Similar to the JSON based queries, you can define a pipeline using the org.springframework.data.mongodb.repository.Aggregation
annotation.
The definition may contain simple placeholders like ?0
as well as SpEL expressions ?#{ … }
.
public interface PersonRepository extends CrudRepository<Person, String> {
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames(); 1
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames(Sort sort); 2
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
List<PersonAggregate> groupByLastnameAnd(String property); 3
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
Slice<PersonAggregate> groupByLastnameAnd(String property, Pageable page); 4
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
Stream<PersonAggregate> groupByLastnameAndFirstnamesAsStream(); 5
@Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
SumValue sumAgeUsingValueWrapper(); 6
@Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
Long sumAge(); 7
@Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
AggregationResults<SumValue> sumAgeRaw(); 8
@Aggregation("{ '$project': { '_id' : '$lastname' } }")
List<String> findAllLastnames(); 9
@Aggregation(pipeline = {
"{ $group : { _id : '$author', books: { $push: '$title' } } }",
"{ $out : 'authors' }"
})
void groupAndOutSkippingOutput(); 10
}
public class PersonAggregate {
private @Id String lastname; 2
private List<String> names;
public PersonAggregate(String lastname, List<String> names) {
// ...
}
// Getter / Setter omitted
}
public class SumValue {
private final Long total; 6 8
public SumValue(Long total) {
// ...
}
// Getter omitted
}
1 | Aggregation pipeline to group first names by lastname in the Person collection returning these as PersonAggregate . |
2 | If Sort argument is present, $sort is appended after the declared pipeline stages so that it only affects the order of the final results after having passed all other aggregation stages.
Therefore, the Sort properties are mapped against the methods return type PersonAggregate which turns Sort.by("lastname") into { $sort : { '_id', 1 } } because PersonAggregate.lastname is annotated with @Id . |
3 | Replaces ?0 with the given value for property for a dynamic aggregation pipeline. |
4 | $skip , $limit and $sort can be passed on via a Pageable argument. Same as in <2>, the operators are appended to the pipeline definition. Methods accepting Pageable can return Slice for easier pagination. |
5 | Aggregation methods can return Stream to consume results directly from an underlying cursor. Make sure to close the stream after consuming it to release the server-side cursor by either calling close() or through try-with-resources . |
6 | Map the result of an aggregation returning a single Document to an instance of a desired SumValue target type. |
7 | Aggregations resulting in single document holding just an accumulation result like e.g. $sum can be extracted directly from the result Document .
To gain more control, you might consider AggregationResult as method return type as shown in <7>. |
8 | Obtain the raw AggregationResults mapped to the generic target wrapper type SumValue or org.bson.Document . |
9 | Like in <6>, a single value can be directly obtained from multiple result `Document`s. |
10 | Skips the output of the $out stage when return type is void . |
在一些场景中,聚合可能需要其他选项,例如最大运行时间、其他日志评论或将数据临时写入磁盘的权限。使用 @Meta
注解通过 maxExecutionTimeMs
、comment
或 allowDiskUse
设置这些选项。
In some scenarios, aggregations might require additional options, such as a maximum run time, additional log comments, or the permission to temporarily write data to disk.
Use the @Meta
annotation to set those options via maxExecutionTimeMs
, comment
or allowDiskUse
.
interface PersonRepository extends CrudRepository<Person, String> {
@Meta(allowDiskUse = true)
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames();
}
或者,使用 @Meta
创建您自己的注解,如下面示例所示。
Or use @Meta
to create your own annotation as shown in the sample below.
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@Meta(allowDiskUse = true)
@interface AllowDiskUse { }
interface PersonRepository extends CrudRepository<Person, String> {
@AllowDiskUse
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames();
}
单一结果的简单类型将检查返回的 Simple-type single-result inspects the returned
|
对于使用 @Aggregation
的存储库方法,不支持 Page
返回类型。但是,你可以使用 Pageable
参数将 $skip
、$limit
和 $sort
添加到管道,并让方法返回 Slice
。
The Page
return type is not supported for repository methods using @Aggregation
. However, you can use a
Pageable
argument to add $skip
, $limit
and $sort
to the pipeline and let the method return Slice
.
Running an Example
Query by Example
Introduction
本章介绍了按示例查询并说明如何使用它。
This chapter provides an introduction to Query by Example and explains how to use it.
按示例查询 (QBE) 是一种具有简单界面的用户友好查询技术。它允许动态创建查询,并且不要求您编写包含字段名称的查询。事实上,按示例查询根本不要求您使用特定于存储的查询语言编写查询。
Query by Example (QBE) is a user-friendly querying technique with a simple interface. It allows dynamic query creation and does not require you to write queries that contain field names. In fact, Query by Example does not require you to write queries by using store-specific query languages at all.
本小节介绍 Query by Example 的核心概念。该信息从 Spring Data Commons 模块中提取。根据您的数据库,字符串匹配支持可能受限。 |
This chapter explains the core concepts of Query by Example. The information is pulled from the Spring Data Commons module. Depending on your database, String matching support can be limited. |
Usage
按示例查询 API 包含四个部分:
The Query by Example API consists of four parts:
-
Probe: The actual example of a domain object with populated fields.
-
ExampleMatcher
: TheExampleMatcher
carries details on how to match particular fields. It can be reused across multiple Examples. -
Example
: AnExample
consists of the probe and theExampleMatcher
. It is used to create the query. -
FetchableFluentQuery
: AFetchableFluentQuery
offers a fluent API, that allows further customization of a query derived from anExample
. Using the fluent API lets you specify ordering projection and result processing for your query.
按示例查询非常适合几种用例:
Query by Example is well suited for several use cases:
-
Querying your data store with a set of static or dynamic constraints.
-
Frequent refactoring of the domain objects without worrying about breaking existing queries.
-
Working independently of the underlying data store API.
按示例查询也有一些限制:
Query by Example also has several limitations:
-
No support for nested or grouped property constraints, such as
firstname = ?0 or (firstname = ?1 and lastname = ?2)
. -
Store-specific support on string matching. Depending on your databases, String matching can support starts/contains/ends/regex for strings.
-
Exact matching for other property types.
在开始使用按示例查询之前,您需要有一个域对象。要开始,请为您的存储库创建一个接口,如下例所示:
Before getting started with Query by Example, you need to have a domain object. To get started, create an interface for your repository, as shown in the following example:
public class Person {
@Id
private String id;
private String firstname;
private String lastname;
private Address address;
// … getters and setters omitted
}
前面的示例显示了一个简单的域对象。您可以用它来创建示例。默认情况下,值为 null 的字段将被忽略,而字符串将使用特定于存储的默认值进行匹配。
The preceding example shows a simple domain object.
You can use it to create an Example
.
By default, fields having null
values are ignored, and strings are matched by using the store specific defaults.
将属性包含到 Query by Example 标准中基于空值能力。使用基本类型( |
Inclusion of properties into a Query by Example criteria is based on nullability.
Properties using primitive types ( |
可以通过使用 of 工厂方法或使用 [ExampleMatcher,查询 by示例。匹配器 ]. Example 是不可变的。以下清单显示了一个简单的示例:
Examples can be built by either using the of
factory method or by using <<`ExampleMatcher`,query-by-example.matchers>>. Example
is immutable.
The following listing shows a simple Example:
Person person = new Person(); 1
person.setFirstname("Dave"); 2
Example<Person> example = Example.of(person); 3
1 | Create a new instance of the domain object. |
2 | Set the properties to query. |
3 | Create the Example . |
您可以使用存储库运行示例查询。要做到这一点,让您的存储库接口扩展 QueryByExampleExecutor<T>。以下清单显示了 QueryByExampleExecutor 接口的摘录:
You can run the example queries by using repositories.
To do so, let your repository interface extend QueryByExampleExecutor<T>
.
The following listing shows an excerpt from the QueryByExampleExecutor
interface:
QueryByExampleExecutor
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … more functionality omitted.
}
Example Matchers
示例不仅限于默认设置。您可以使用 ExampleMatcher 指定字符串匹配、null 处理和特定于属性的设置的默认值,如下例所示:
Examples are not limited to default settings.
You can specify your own defaults for string matching, null handling, and property-specific settings by using the ExampleMatcher
, as shown in the following example:
Person person = new Person(); 1
person.setFirstname("Dave"); 2
ExampleMatcher matcher = ExampleMatcher.matching() 3
.withIgnorePaths("lastname") 4
.withIncludeNullValues() 5
.withStringMatcher(StringMatcher.ENDING); 6
Example<Person> example = Example.of(person, matcher); 7
1 | Create a new instance of the domain object. |
2 | Set properties. |
3 | Create an ExampleMatcher to expect all values to match.
It is usable at this stage even without further configuration. |
4 | Construct a new ExampleMatcher to ignore the lastname property path. |
5 | Construct a new ExampleMatcher to ignore the lastname property path and to include null values. |
6 | Construct a new ExampleMatcher to ignore the lastname property path, to include null values, and to perform suffix string matching. |
7 | Create a new Example based on the domain object and the configured ExampleMatcher . |
默认情况下,ExampleMatcher 期望探测器上设置的所有值都匹配。如果您想要获得与隐式定义的任何谓词匹配的结果,请使用 ExampleMatcher.matchingAny()。
By default, the ExampleMatcher
expects all values set on the probe to match.
If you want to get results matching any of the predicates defined implicitly, use ExampleMatcher.matchingAny()
.
您可以指定单个属性(例如“firstname”和“lastname”,或对于嵌套属性,“address.city”)的行为。您可以用匹配选项和大写/小写敏感性对其进行微调,如下例所示:
You can specify behavior for individual properties (such as "firstname" and "lastname" or, for nested properties, "address.city"). You can tune it with matching options and case sensitivity, as shown in the following example:
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
}
配置匹配器选项的另一种方法是使用 lambda(在 Java 8 中引入)。此方法创建一个回调,要求实现者修改匹配器。您无需返回匹配器,因为配置选项保存在匹配器实例中。以下示例显示了一个使用 lambda 的匹配器:
Another way to configure matcher options is to use lambdas (introduced in Java 8). This approach creates a callback that asks the implementor to modify the matcher. You need not return the matcher, because configuration options are held within the matcher instance. The following example shows a matcher that uses lambdas:
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", match -> match.endsWith())
.withMatcher("firstname", match -> match.startsWith());
}
由 Example 创建的查询使用配置的合并视图。默认匹配设置可以设置在 ExampleMatcher 级别,而各个设置可以应用于特定的属性路径。除非显式定义,否则在 ExampleMatcher 上设置的设置将由属性路径设置继承。属性补丁上的设置优先于默认设置。下表描述了各种 ExampleMatcher 设置的范围:
Queries created by Example
use a merged view of the configuration.
Default matching settings can be set at the ExampleMatcher
level, while individual settings can be applied to particular property paths.
Settings that are set on ExampleMatcher
are inherited by property path settings unless they are defined explicitly.
Settings on a property patch have higher precedence than default settings.
The following table describes the scope of the various ExampleMatcher
settings:
Setting | Scope |
---|---|
Null-handling |
|
String matching |
|
Ignoring properties |
Property path |
Case sensitivity |
|
Value transformation |
Property path |
Fluent API
QueryByExampleExecutor 提供了另一种我们到目前为止尚未提到的方法:<S extends T,R> R findBy(Example<S> example,Function<FluentQuery.FetchableFluentQuery<S>,R> queryFunction)。与其他方法一样,它执行从 Example 派生的查询。但是,借助第二个参数,您可以控制执行的方面,否则您将无法动态控制。您可以通过在第二个参数中调用 FetchableFluentQuery 的各种方法来执行此操作。sortBy 让我们为您指定结果的排序方式。as 允许你指定想要将结果转换为的类型。project 限制了查询的属性。first、firstValue、one、oneValue、all、page、stream、count 和 exists 定义了您获得什么样的结果,以及在可用结果多于预期数量时查询将如何进行。
QueryByExampleExecutor
offers one more method, which we did not mention so far: <S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction)
.
As with other methods, it executes a query derived from an Example
.
However, with the second argument, you can control aspects of that execution that you cannot dynamically control otherwise.
You do so by invoking the various methods of the FetchableFluentQuery
in the second argument.
sortBy
lets you specify an ordering for your result.
as
lets you specify the type to which you want the result to be transformed.
project
limits the queried attributes.
first
, firstValue
, one
, oneValue
, all
, page
, stream
, count
, and exists
define what kind of result you get and how the query behaves when more than the expected number of results are available.
Optional<Person> match = repository.findBy(example,
q -> q
.sortBy(Sort.by("lastname").descending())
.first()
);
Running an Example
以下示例演示了在使用存储库(在这种情况下为 Person
对象)时如何按示例查询:
The following example shows how to query by example when using a repository (of Person
objects, in this case):
public interface PersonRepository extends QueryByExampleExecutor<Person> {
}
public class PersonService {
@Autowired PersonRepository personRepository;
public List<Person> findPeople(Person probe) {
return personRepository.findAll(Example.of(probe));
}
}
Scrolling
滚动是迭代处理较大结果集区块的一种更细粒度的处理方式。滚动包含稳定的排序、滚动类型(基于偏移或基于键集的滚动)以及结果限制。你可以使用属性名称定义简单的排序表达式,并使用 xref:repositories/query-methods-details.adoc#repositories.limit-query-result[Top
或 First
关键字通过查询派生定义静态结果限制。你可以联接表达式以将多个条件收集到一个表达式中。
Scrolling is a more fine-grained approach to iterate through larger results set chunks.
Scrolling consists of a stable sort, a scroll type (Offset- or Keyset-based scrolling) and result limiting.
You can define simple sorting expressions by using property names and define static result limiting using the Top
or First
keyword through query derivation.
You can concatenate expressions to collect multiple criteria into one expression.
滚动查询返回一个 Window<T>
,它允许获取滚动位置以继续获取下一个 Window<T>
直到您的应用程序使用整个查询结果。类似于通过获取下一批结果使用 Java Iterator<List<…>>
消费查询结果,查询结果滚动允许您通过 Window.positionAt(…)
访问 ScrollPosition
。
Scroll queries return a Window<T>
that allows obtaining the scroll position to resume to obtain the next Window<T>
until your application has consumed the entire query result.
Similar to consuming a Java Iterator<List<…>>
by obtaining the next batch of results, query result scrolling lets you access the a ScrollPosition
through Window.positionAt(…)
.
Window<User> users = repository.findFirst10ByLastnameOrderByFirstname("Doe", ScrollPosition.offset());
do {
for (User u : users) {
// consume the user
}
// obtain the next Scroll
users = repository.findFirst10ByLastnameOrderByFirstname("Doe", users.positionAt(users.size() - 1));
} while (!users.isEmpty() && users.hasNext());
WindowIterator
提供了一个实用工具,可通过消除检查是否存在下一个 Window
和应用 ScrollPosition
的需求来简化跨 Window
的滚动。
WindowIterator
provides a utility to simplify scrolling across Window`s by removing the need to check for the presence of a next `Window
and applying the ScrollPosition
.
WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
.startingAt(OffsetScrollPosition.initial());
while (users.hasNext()) {
User u = users.next();
// consume the user
}
Scrolling using Offset
偏移量滚动类似于分页,它使用偏移量计数器跳过一定数目的结果,并让数据源仅从给定偏移量开始返回结果。此简单机制可避免向客户端应用程序发送较大的结果。但是,在服务器返回结果之前,大多数数据库都需要实现完整的查询结果。
Offset scrolling uses similar to pagination, an Offset counter to skip a number of results and let the data source only return results beginning at the given Offset. This simple mechanism avoids large results being sent to the client application. However, most databases require materializing the full query result before your server can return the results.
OffsetScrollPosition
with Repository Query Methodsinterface UserRepository extends Repository<User, Long> {
Window<User> findFirst10ByLastnameOrderByFirstname(String lastname, OffsetScrollPosition position);
}
WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
.startingAt(OffsetScrollPosition.initial()); 1
1 | Start from the initial offset at position 0 . |
Scrolling using Keyset-Filtering
基于偏移量的方法需要大多数数据库在服务器返回结果之前实现全部结果的物化。因此,当客户端只看到请求结果的一部分时,服务器需要构建全部结果,这会导致额外的负载。
Offset-based requires most databases require materializing the entire result before your server can return the results. So while the client only sees the portion of the requested results, your server needs to build the full result, which causes additional load.
键集过滤方法通过利用数据库的内置功能来检索结果子集,旨在降低单个查询的计算和 I/O 需求。此方法维护一组键用于通过查询中传入键来恢复滚动,有效修正筛选条件。
Keyset-Filtering approaches result subset retrieval by leveraging built-in capabilities of your database aiming to reduce the computation and I/O requirements for individual queries. This approach maintains a set of keys to resume scrolling by passing keys into the query, effectively amending your filter criteria.
键集过滤的核心思想是使用稳定的排序顺序开始检索结果。一旦想要滚动到下一个块,就可以获取用于重建排序结果中位置的 ScrollPosition
。ScrollPosition
捕获当前 Window
中最后一个实体的键集。要运行查询,重建会重写条件子句以包括所有排序字段和主键,以便数据库可以利用潜在的索引来运行查询。数据库只需要根据给定的键集位置构建一个更小的结果,而无需完全实现一个较大的结果,然后跳过一些结果直到达到特定的偏移量。
The core idea of Keyset-Filtering is to start retrieving results using a stable sorting order.
Once you want to scroll to the next chunk, you obtain a ScrollPosition
that is used to reconstruct the position within the sorted result.
The ScrollPosition
captures the keyset of the last entity within the current Window
.
To run the query, reconstruction rewrites the criteria clause to include all sort fields and the primary key so that the database can leverage potential indexes to run the query.
The database needs only constructing a much smaller result from the given keyset position without the need to fully materialize a large result and then skipping results until reaching a particular offset.
键集过滤需要键集属性(用于排序的属性)为非空。此限制适用于由于比较运算符对存储特定的 null
值处理以及由于需要针对索引来源运行查询。针对空属性的键集过滤会导致意外的结果。
Keyset-Filtering requires the keyset properties (those used for sorting) to be non-nullable.
This limitation applies due to the store specific null
value handling of comparison operators as well as the need to run queries against an indexed source.
Keyset-Filtering on nullable properties will lead to unexpected results.
KeysetScrollPosition
with Repository Query Methodsinterface UserRepository extends Repository<User, Long> {
Window<User> findFirst10ByLastnameOrderByFirstname(String lastname, KeysetScrollPosition position);
}
WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
.startingAt(ScrollPosition.keyset()); 1
1 | Start at the very beginning and do not apply additional filtering. |
当数据库包含与排序字段相匹配的索引时,键集过滤效果最佳,因此静态排序效果很好。应用键集过滤的滚动查询需要将排序顺序中使用的属性通过查询返回,而这些属性必须映射在返回的实体中。
Keyset-Filtering works best when your database contains an index that matches the sort fields, hence a static sort works well. Scroll queries applying Keyset-Filtering require to the properties used in the sort order to be returned by the query, and these must be mapped in the returned entity.
可以使用接口和 DTO 投影,但是确保包括已对其进行排序的所有属性,以避免键集提取失败。
You can use interface and DTO projections, however make sure to include all properties that you’ve sorted by to avoid keyset extraction failures.
在指定 Sort
顺序时,包含与查询相关的排序属性就足够了;如果您不想确保唯一查询结果,就不需要这样做。键集查询机制会通过包括主键(或复合主键的任何剩余部分)来修正排序顺序,确保每个查询结果都是唯一的。
When specifying your Sort
order, it is sufficient to include sort properties relevant to your query;
You do not need to ensure unique query results if you do not want to.
The keyset query mechanism amends your sort order by including the primary key (or any remainder of composite primary keys) to ensure each query result is unique.
Sorting Results
MongoDB存储库允许使用各种方法来定义排序顺序。让我们来看下面的示例:
MongoDB repositories allow various approaches to define sorting order. Let’s take a look at the following example:
- Imperative
-
public interface PersonRepository extends MongoRepository<Person, String> { List<Person> findByFirstnameSortByAgeDesc(String firstname); 1 List<Person> findByFirstname(String firstname, Sort sort); 2 @Query(sort = "{ age : -1 }") List<Person> findByFirstname(String firstname); 3 @Query(sort = "{ age : -1 }") List<Person> findByLastname(String lastname, Sort sort); 4 }
1 | Static sorting derived from method name. SortByAgeDesc results in { age : -1 } for the sort parameter. |
2 | Dynamic sorting using a method argument.
Sort.by(DESC, "age") creates { age : -1 } for the sort parameter. |
3 | Static sorting via Query annotation.
Sort parameter applied as stated in the sort attribute. |
4 | Default sorting via Query annotation combined with dynamic one via a method argument. Sort.unsorted()
results in { age : -1 } .
Using Sort.by(ASC, "age") overrides the defaults and creates { age : 1 } .
Sort.by
(ASC, "firstname") alters the default and results in { age : -1, firstname : 1 } .
|
Index Hints
`@Hint`注释允许覆盖MongoDB的默认索引选择,并强制数据库使用指定的索引。
The @Hint
annotation allows to override MongoDB’s default index selection and forces the database to use the specified index instead.
@Hint("lastname-idx") 1
List<Person> findByLastname(String lastname);
@Query(value = "{ 'firstname' : ?0 }", hint = "firstname-idx") 2
List<Person> findByFirstname(String firstname);
1 | Use the index with name lastname-idx . |
2 | The @Query annotation defines the hint alias which is equivalent to adding the @Hint annotation. |
有关索引创建的更多信息,请参阅 Collection Management 部分。
For more information about index creation please refer to the Collection Management section.
Collation Support
除了 general Collation Support 之外,存储库还允许为各种操作定义排序规则。
Next to the general Collation Support repositories allow to define the collation for various operations.
public interface PersonRepository extends MongoRepository<Person, String> {
@Query(collation = "en_US") 1
List<Person> findByFirstname(String firstname);
@Query(collation = "{ 'locale' : 'en_US' }") 2
List<Person> findPersonByFirstname(String firstname);
@Query(collation = "?1") 3
List<Person> findByFirstname(String firstname, Object collation);
@Query(collation = "{ 'locale' : '?1' }") 4
List<Person> findByFirstname(String firstname, String collation);
List<Person> findByFirstname(String firstname, Collation collation); 5
@Query(collation = "{ 'locale' : 'en_US' }")
List<Person> findByFirstname(String firstname, @Nullable Collation collation); 6
}
1 | Static collation definition resulting in { 'locale' : 'en_US' } . |
2 | Static collation definition resulting in { 'locale' : 'en_US' } . |
3 | Dynamic collation depending on 2nd method argument. Allowed types include String (eg. 'en_US'), Locacle (eg. Locacle.US)
and Document (eg. new Document("locale", "en_US")) |
4 | Dynamic collation depending on 2nd method argument. |
5 | Apply the Collation method parameter to the query. |
6 | The Collation method parameter overrides the default collation from @Query if not null. |
如果你为存储库查找器方法启用了自动索引创建,则在创建索引时将包含潜在的静态排序定义,如 (1) 和 (2) 所示。
In case you enabled the automatic index creation for repository finder methods a potential static collation definition, as shown in (1) and (2), will be included when creating the index.
最具体的 Collation
优于其他可能定义的 @@。这意味着方法参数优于查询方法注解,而查询方法注解优于域类型注解。
The most specifc Collation
outrules potentially defined others. Which means Method argument over query method annotation over domain type annotation.
为了精简整个代码库中整理规则属性的使用,还可以使用`@Collation`注释,它可以用作上述注释的元注释。应用相同的规则和位置,此外,直接使用`@Collation`会取代在`@Query`和其他注释中定义的任何整理规则值。这意味着,如果通过`@Query`和`@Collation`声明了整理规则,则会选择`@Collation`中的那个。
To streamline usage of collation attributes throughout the codebase it is also possible to use the @Collation
annotation, which serves as a meta annotation for the ones mentioned above.
The same rules and locations apply, plus, direct usage of @Collation
supersedes any collation values defined on @Query
and other annotations.
Which means, if a collation is declared via @Query
and additionally via @Collation
, then the one from @Collation
is picked.
@Collation
@Collation("en_US") 1
class Game {
// ...
}
interface GameRepository extends Repository<Game, String> {
@Collation("en_GB") 2
List<Game> findByTitle(String title);
@Collation("de_AT") 3
@Query(collation="en_GB")
List<Game> findByDescriptionContaining(String keyword);
}
1 | Instead of @Document(collation=…) . |
2 | Instead of @Query(collation=…) . |
3 | Favors @Collation over meta usage. |
Read Preferences
`@ReadPreference`注释允许你配置MongoDB的ReadPreferences。
The @ReadPreference
annotation allows you to configure MongoDB’s ReadPreferences.
@ReadPreference("primaryPreferred") 1
public interface PersonRepository extends CrudRepository<Person, String> {
@ReadPreference("secondaryPreferred") 2
List<Person> findWithReadPreferenceAnnotationByLastname(String lastname);
@Query(readPreference = "nearest") 3
List<Person> findWithReadPreferenceAtTagByFirstname(String firstname);
List<Person> findWithReadPreferenceAtTagByFirstname(String firstname); 4
1 | Configure read preference for all repository operations (including inherited, non custom implementation ones) that do not have a query-level definition. Therefore, in this case the read preference mode will be primaryPreferred |
2 | Use the read preference mode defined in annotation ReadPreference , in this case secondaryPreferred |
3 | The @Query annotation defines the read preference mode alias which is equivalent to adding the @ReadPreference annotation. |
4 | This query will use the read preference mode defined in the repository. |
`MongoOperations`和`Query`API为`ReadPreference`提供了更精细的控制。 The |