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:

Example 1. PersonRepository with query methods
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.
Reactive
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,以将 sortlimitoffset 参数传递给查询以减少负载和网络流量。返回的 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);

我们不支持引用在域类中映射为 DBRef 的参数。

We do not support referring to parameters that are mapped as DBRef in the domain class.

Example 2. Supported keywords for query methods
Keyword Sample Logical result

After

findByBirthdateAfter(Date date)

{"birthdate" : {"$gt" : date}}

GreaterThan

findByAgeGreaterThan(int age)

{"age" : {"$gt" : age}}

GreaterThanEqual

findByAgeGreaterThanEqual(int age)

{"age" : {"$gte" : age}}

Before

findByBirthdateBefore(Date date)

{"birthdate" : {"$lt" : date}}

LessThan

findByAgeLessThan(int age)

{"age" : {"$lt" : age}}

LessThanEqual

findByAgeLessThanEqual(int age)

{"age" : {"$lte" : age}}

Between

findByAgeBetween(int from, int to) findByAgeBetween(Range<Integer> range)

{"age" : {"$gt" : from, "$lt" : to}} lower / upper bounds ($gt / $gte & $lt / $lte) according to Range

In

findByAgeIn(Collection ages)

{"age" : {"$in" : [ages…​]}}

NotIn

findByAgeNotIn(Collection ages)

{"age" : {"$nin" : [ages…​]}}

IsNotNull, NotNull

findByFirstnameNotNull()

{"firstname" : {"$ne" : null}}

IsNull, Null

findByFirstnameNull()

{"firstname" : null}

Like, StartingWith, EndingWith

findByFirstnameLike(String name)

{"firstname" : name} (name as regex)

NotLike, IsNotLike

findByFirstnameNotLike(String name)

{"firstname" : { "$not" : name }} (name as regex)

Containing on String

findByFirstnameContaining(String name)

{"firstname" : name} (name as regex)

NotContaining on String

findByFirstnameNotContaining(String name)

{"firstname" : { "$not" : name}} (name as regex)

Containing on Collection

findByAddressesContaining(Address address)

{"addresses" : { "$in" : address}}

NotContaining on Collection

findByAddressesNotContaining(Address address)

{"addresses" : { "$not" : { "$in" : address}}}

Regex

findByFirstnameRegex(String firstname)

{"firstname" : {"$regex" : firstname }}

(No keyword)

findByFirstname(String name)

{"firstname" : name}

Not

findByFirstnameNot(String name)

{"firstname" : {"$ne" : name}}

Near

findByLocationNear(Point point)

{"location" : {"$near" : [x,y]}}

Near

findByLocationNear(Point point, Distance max)

{"location" : {"$near" : [x,y], "$maxDistance" : max}}

Near

findByLocationNear(Point point, Distance min, Distance max)

{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}

Within

findByLocationWithin(Circle circle)

{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}

Within

findByLocationWithin(Box box)

{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}

IsTrue, True

findByActiveIsTrue()

{"active" : true}

IsFalse, False

findByActiveIsFalse()

{"active" : false}

Exists

findByLocationExists(boolean exists)

{"location" : {"$exists" : exists }}

IgnoreCase

findByUsernameIgnoreCase(String username)

{"username" : {"$regex" : "^username$", "$options" : "i" }}

如果属性条件比较一个文档,则文档中字段的顺序和精确相等很重要。

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:

Advanced 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:

Example 3. Using 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}}

响应式地理空间存储库查询支持域类型和响应式包装器类型中的 GeoResult<T> 结果。不支持 GeoPageGeoResults,因为它们与使用预先计算平均距离的延迟结果方法相矛盾。但是,你仍然可以自己传递 Pageable 参数来分页结果。

Reactive Geo-spatial repository queries support the domain type and GeoResult<T> results within a reactive wrapper type. GeoPage and GeoResults are not supported as they contradict the deferred result approach with pre-calculating the average distance. However, you can still pass in a Pageable argument to page results yourself.

使用 MetricDistance,将导致添加一个 $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.)

在目标属性上使用 @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) 会强制使用 $nearSphere 运算符。

Using @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) on the target property forces usage of the $nearSphere operator.

  • 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.

String 参数值在绑定过程中会被转义,这意味着不可能通过参数添加 MongoDB 特定运算符。

String parameter values are escaped during the binding process, which means that it is not possible to add MongoDB specific operators through the argument.

您还可以使用 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 对象的 firstnamelastnameId 属性。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 来扩展:EvaluationContextExtensionReactiveEvaluationContextExtension。查询 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() { ... });
    }
}

自己引导 MongoRepositoryFactory 并不会意识到应用程序上下文,并且需要进一步的配置才能拾取查询 SPI 扩展。

Bootstrapping MongoRepositoryFactory yourself is not application context-aware and requires further configuration to pick up Query SPI extensions.

响应式查询方法可以使用 org.springframework.data.spel.spi.ReactiveEvaluationContextExtension

Reactive query methods can make use of org.springframework.data.spel.spi.ReactiveEvaluationContextExtension.

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 ?#{ … }.

Example 4. Aggregating Repository Method
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 注解通过 maxExecutionTimeMscommentallowDiskUse 设置这些选项。

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();
}

单一结果的简单类型将检查返回的 Document,并检查以下内容:

Simple-type single-result inspects the returned Document and checks for the following:

  1. Only one entry in the document, return it.

  2. Two entries, one is the _id value. Return the other.

  3. Return for the first value assignable to the return type.

  4. Throw an exception if none of the above is applicable.

对于使用 @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: The ExampleMatcher carries details on how to match particular fields. It can be reused across multiple Examples.

  • Example: An Example consists of the probe and the ExampleMatcher. It is used to create the query.

  • FetchableFluentQuery: A FetchableFluentQuery offers a fluent API, that allows further customization of a query derived from an Example. 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:

Sample Person object
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 标准中基于空值能力。使用基本类型(intdouble、…)的属性始终会包含,除非 <<`ExampleMatcher` 忽略属性路径,query-by-example.matchers>>。

Inclusion of properties into a Query by Example criteria is based on nullability. Properties using primitive types (int, double, …) are always included unless the <<`ExampleMatcher` ignores the property path,query-by-example.matchers>>.

可以通过使用 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:

Example 5. 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:

The 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:

Example 6. Example matcher with customized matching
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:

Configuring matcher options
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:

Configuring matcher options with 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:

Table 1. Scope of ExampleMatcher settings
Setting Scope

Null-handling

ExampleMatcher

String matching

ExampleMatcher and property path

Ignoring properties

Property path

Case sensitivity

ExampleMatcher and property path

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.

Use the fluent API to get the last of potentially many results, ordered by lastname.
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):

Example 7. Query by Example using a repository
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[TopFirst 关键字通过查询派生定义静态结果限制。你可以联接表达式以将多个条件收集到一个表达式中。

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.

Example 8. Using OffsetScrollPosition with Repository Query Methods
interface 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.

键集过滤的核心思想是使用稳定的排序顺序开始检索结果。一旦想要滚动到下一个块,就可以获取用于重建排序结果中位置的 ScrollPositionScrollPosition 捕获当前 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.

Using KeysetScrollPosition with Repository Query Methods
interface 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:

Example 9. Sorting Query Results
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 }.
Reactive
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {

    Flux<Person> findByFirstnameSortByAgeDesc(String firstname);

    Flux<Person> findByFirstname(String firstname, Sort sort);

    @Query(sort = "{ age : -1 }")
    Flux<Person> findByFirstname(String firstname);

    @Query(sort = "{ age : -1 }")
    Flux<Person> findByLastname(String lastname, Sort sort);
}

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.

Example 10. Example of index hints
@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.

Example 11. Using @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.

Example 12. Example of read preferences
@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 MongoOperations and Query API offer more fine grained control for ReadPreference.